2010-10-08 22 views
2

J'ai un design où j'ai un std::list de pointeurs de base que je voudrais transformer en une liste parallèle qui ajoute un comportement. Le problème que j'ai est que l'objet que j'essaie d'utiliser pour faire la transformation ne sait pas quels sont les types réels quand il est appelé.Transformer une liste de pointeurs en classe de base

Il est tout à fait possible qu'il me manque quelque chose de subtil et qu'il y a une solution facile. Cependant, si c'est un défaut de conception (j'ai vu cela suggéré dans d'autres articles), quelle est la meilleure façon d'aborder cela?

Supposons que le texte suivant:

class Sprite { /* ... */ }; 
class Character : public Sprite {}; 
class Markup : public Sprite {}; 

Ceux-ci sont réalisés (par rapport à une certaine entrée) dans un std::list< Sprite * >. Ce que je voudrais faire, c'est finalement prendre la liste et la transformer en une structure parallèle adaptée aux opérations de sortie. Par exemple, étant donné:

class HTMLSprite { /* ... */ }; 
class HTMLCharacter : public HTMLSprite {}; 
class HTMLMarkup : public HTMLSprite {}; 

Je voudrais idéalement faire quelque chose comme

std::transform(sprites.begin(), sprites.end(), html.begin(), HTMLConvert); 

avec quelque chose comme

struct HTMLConvert_ { 
    HTMLSprite * operator() (const Character * c) { return new HTMLCharacter(); } 
    HTMLSprite * operator() (const Markup * c) { return new HTMLMarkup(); } 
} HTMLConvert; 

Maintenant, je reçois l'erreur

call of `(HTMLConvert) (const Sprite* const&)' is ambiguous 
HTMLSprite* HTMLConvert::operator()(const Character*) const <near match> 

Ce qui m'amène à ma question n. Quelle est la meilleure solution à ce problème - refonte ou autre?

Merci.

+0

Problème classique pour le modèle de visiteur. – Omnifarious

Répondre

3

Outre la suggestion de JoshD, vous pouvez garder la porte ouverte pour d'autres transformations en utilisant le visitor pattern.

Ajouter une méthode dispatch_visit à la hiérarchie Sprite, en utilisant des types de retour covariants:

class Sprite{ 
    virtual HTMLSprite * dispatch_visit(HTMLConvert_ const &c) const = 0; 
}; 
class Character : public Sprite { 
    virtual HTMLCharacter * dispatch_visit(HTMLConvert_ const &c) const 
     { return c(this); } 
}; 
class Markup : public Sprite { 
    virtual HTMLMarkup * dispatch_visit(HTMLConvert_ const &c) const 
     { return c(this); } 
}; 

Cela permet à chaque objet de notifier le convertisseur de son type dynamique - essentiellement, une répartition dynamique dans le type parallèle, ou même une hiérarchie de type parallèle. Tout le reste fonctionne à peu près comme votre code est écrit ... le meilleur candidat est choisi parmi les operator()() fonctions du convertisseur, basé sur le type de paramètre statique.

Oh, vous devrez ajouter la fonction « manquant » au convertisseur:

struct HTMLConvert_ { 
    HTMLSprite * operator() (const Sprite * c) { return c->dispatch_visit(*this); } 
    HTMLCharacter * operator() (const Character * c) { return new HTMLCharacter(); } 
    HTMLMarkup * operator() (const Markup * c) { return new HTMLMarkup(); } 
} HTMLConvert; 

Hmm, cette fonction répétitive peut être encapsulé par un modèle ... cela ne semble travailler dans C++ 0x si vous souhaitez que le modèle visitable détermine automatiquement le type de retour de dispatch_visit. Si vous n'aimez pas principal_base vous pouvez le factoriser.

#include <functional> 

template< class T > 
struct principal_base 
    { typedef void type; }; 

template< class Client, class Visitor, 
    class Base = typename principal_base<Client>::type > 
struct visitable : 
    virtual visitable< typename principal_base<Client>::type, Visitor > { 
    virtual typename std::result_of< Visitor(Client *) >::type 
    dispatch_visit(Visitor const &v) const 
     { return v(static_cast< Client const * >(this)); } 
}; 

template< class Client, class Visitor > 
struct visitable< Client, Visitor, void > { 
    virtual typename std::result_of< Visitor(Client *) >::type 
    dispatch_visit(Visitor const &v) const = 0; 
}; 

class HTMLSprite { /* ... */ }; 
class HTMLCharacter : public HTMLSprite {}; 
class HTMLMarkup : public HTMLSprite {}; 

class Sprite; 
class Character; 
class Markup; 

struct HTMLConvert_ { 
    HTMLSprite * operator() (const Sprite * c); 
    HTMLCharacter * operator() (const Character * c); 
    HTMLMarkup * operator() (const Markup * c); 
} HTMLConvert; 

class Sprite : public visitable< Sprite, HTMLConvert_ > {}; 
template<> struct principal_base<Character> 
    { typedef Sprite type; }; 
class Character : public Sprite, visitable< Character, HTMLConvert_ > {}; 
template<> struct principal_base<Markup> 
    { typedef Sprite type; }; 
class Markup : public Sprite, visitable< Markup, HTMLConvert_ > {}; 

//class Invalid : Character, Markup {}; 

HTMLSprite * HTMLConvert_::operator() (const Sprite * c) 
    { return c->dispatch_visit(*this); } 
HTMLCharacter * HTMLConvert_::operator() (const Character * c) 
    { return new HTMLCharacter(); } 
HTMLMarkup * HTMLConvert_::operator() (const Markup * c) 
    { return new HTMLMarkup(); } 
+0

Cela ressemble à ce que je pourrais finir par faire. Il est regrettable que je ne puisse pas avoir un modèle 'dispatch_visit' virtuel pour éviter la redondance. Je suis rapidement dans les limites de ma compréhension du langage C++ ici. – ezpz

+0

@ezpz: Je ne vois pas pourquoi 'dispatch_visit' devrait être impossible; J'ai essayé d'en trouver un pour cette réponse, mais je me suis retrouvé coincé avec un opérateur 'decltype' /' typeof'. C'est donc plus facile en C++ 0x, dans tous les cas. (Vous ne pouvez pas avoir une fonction virtuelle basée sur un modèle, mais vous pouvez avoir une fonction virtuelle dans une classe de base basée sur un modèle.) – Potatoswatter

+0

@ezpz: Mise à jour avec un tel modèle. Cela nécessite C++ 0x comme écrit, mais vous pourriez contourner cela. – Potatoswatter

2

Je suggère d'ajouter la fonction de conversion au type HTML à chaque classe. Ainsi, vous auriez une fonction virtuelle Convert en tant que membre de Sprite et chaque classe dérivée peut la remplacer. Ensuite, lorsque vous appelez la fonction Convert sur votre liste de Sprite *, il appellera le convertisseur approprié. Vous devez transmettre le type de retour (HTMLSprite).

Il y a probablement des manières plus élégantes, mais cette idée vous permet d'utiliser des fonctions virtuelles. Le problème avec votre suggestion est que les pointeurs dans le tableau sont tous de type Sprite * indépendamment de ce qu'ils pointent vers, donc Sprite * est ce qui sera passé à votre fonction. Au lieu de cela, utilisez quelque chose comme mem_fun pour faire une structure qui appellera le membre; ce appellera la fonction appropriée par un appel virtuel:

std::transform(sprites.begin(), sprites.end(), html.begin(), mem_fun(&Sprite::Convert)); 

commentaire si vous avez besoin de moi quoi que ce soit clarifier.

+1

Le problème que je vois avec cela est que maintenant ma classe 'Sprite' doit être au courant de toute cible de conversion potentielle. J'espérais exposer une interface et supporter les changements de représentations arbitraires sans avoir la structure interne au courant des détails de ces conversions. – ezpz

+0

Vous devrez peut-être sauter par-dessus des cerceaux pour cela. Je comprends que ce n'est pas idéal, mais pour convertir correctement la classe, vous aurez besoin d'un accès privé. Cela signifie soit un membre ou un ami (ou une classe complètement exposée), dont l'un ou l'autre doit être déclaré dans la définition de la classe. La classe entièrement exposée est probablement l'option la plus propre, mais vous aurez toujours besoin d'un moyen d'identifier le type pointé par un sprite *. Cela pourrait être fait avec une fonction virtuelle 'whatAmI'. Mais, comme je l'ai dit, ça devient salissant rapidement. Quelqu'un d'autre ici a probablement une solution élégante; Cela m'intéresserait. – JoshD

+0

@ezpz: Vous pouvez alors avoir une classe de traits parallèles pour chaque type de conversion, tout en utilisant le polymorphisme du modèle de visiteur. http://en.wikipedia.org/wiki/Visitor_pattern – Potatoswatter

0

Comme votre converti devra connaître tous les types de convertir et aussi effectuer une fonction de cartographie.

Voici mon hack

struct HTMLConvert_ { 
    HTMLSprite * operator() (const Sprite* const& sp) { 
    Character const * c = dynamic_cast<Character const *>(sp); 
    if(c) 
     return new HTMLCharacter (c); 

    Markup const * m = dynamic_cast<Markup const *>(sp); 
    if(c) 
     return new HTMLMarkup (m); 
    } 
} HTMLConvert; 
+0

Cela se transforme rapidement en une instruction de basculement RTTI. Dans le cas de plusieurs types ce n'est pas vraiment préférable - j'aurais dû mentionner que j'ai beaucoup de types :) – ezpz

1

Que diriez-vous d'une usine abstraite?

 +-----------------+       +--------+   
     | RenderFactory |       | Sprite | 
     |=================|       +--------+ 
     | CreateSprite() |         /\ 
     | CreateMarkup() |       +------------------+ 
     +-----------------+       |     | 
       /\       +------------+ +-------------+ 
     +-----------------+    ....>| HTMLSprite | | PlainSprite | 
     |     |    : +------------+ +-------------+ 
+----------------+ +----------------+ : 
| PlainFactory | | HTMLFactory | : 
|================| |================| :     +--------+ 
| CreateSprite() | | CreateSprite() |.:...    | Markup | 
| CreateMarkup() | | CreateMarkup() |.. :    +--------+ 
+----------------+ +----------------+ :     /\ 
              :   +----------------+ 
              :   |    | 
              : +------------+ +-------------+ 
              ..> | HTMLMarkup | | PlainMarkup | 
               +------------+ +-------------+ 
+0

Wow - dessin ascii-licious :) Bien que, cela ressemble à doubler le travail que j'ai besoin de faire à l'étape d'analyse. L'idée est que je ne vais analyser qu'une seule fois et permettre plusieurs formes de sortie via cette transformation. – ezpz