2010-01-18 6 views
5

J'utilise des pseudo-interfaces en C++, c'est-à-dire des classes abstraites pures. Supposons que j'ai trois interfaces, IFoo, IBar et IQuux. J'ai aussi une classe Fred qui met en œuvre tous les trois d'entre eux:Vérification de l'implémentation de l'interface de compilation dans C++

interface IFoo 
{ 
    void foo (void); 
} 

interface IBar 
{ 
    void bar (void); 
} 

interface IQuux 
{ 
    void quux (void); 
} 

class Fred : implements IFoo, IBar, IQuux 
{ 
} 

Je veux déclarer une méthode qui accepte un objet qui implémente IFoo et IBar - un Fred fonctionnerait, par exemple. La seule compilation façon de le faire, je peux imaginer est de définir un troisième IFooAndBar d'interface qui implémente les deux, et redéclarer Fred:

interface IFooAndBar : extends IFoo, IBar 
{ 
} 

class Fred : implements IFooAndBar, IQuux 
{ 
} 

Maintenant, je peux déclarer ma méthode comme la réception d'un IFooAndBar *. Jusqu'ici tout va bien.


Cependant, ce qui se passe si je veux aussi une autre méthode qui accepte BIRA et IQuux? J'ai essayé de déclarer une nouvelle interface IBarAndQuux et déclarant Fred comme héritant à la fois:

class IFooAndBar : IFoo, IBar 
{ 
}; 


class IBarAndQuux : IBar, IQuux 
{ 
}; 


class Fred : IFooAndBar, IBarAndQuux 
{ 
}; 

Cela fonctionne quand je passe comme Fred IFooAndBar à une méthode; Cependant, quand je tente d'appeler la barre de Fred() directement, gcc se plaint:

error: request for member ‘bar’ is ambiguous 
error: candidates are: void IBar::bar() 
error:     void IBar::bar() 

ce qui rend cette solution plus ou moins inutile.


Ma prochaine tentative a été de déclarer Fred comme héritant des trois interfaces individuelles, et en faisant la méthode accepte l'une des interfaces hybrides en tant que paramètre:

class Fred : public IFoo, public IBar, public IBaz 
{ 

}; 

void doTest (IBarAndBaz* pObj) 
{ 
    pObj->bar(); 
    pObj->baz(); 
} 

Lorsque je tente de passer Fred comme IBarAndBaz paramètre *, je reçois une erreur, comme prévu:

error: cannot convert ‘Fred*’ to ‘IBarAndBaz*’ for argument ‘1’ to ‘void doTest(IBarAndBaz*)’ 

dynamic_cast <> aussi p roduces une erreur (que je ne comprends pas)

error: cannot dynamic_cast ‘pFred’ (of type ‘class Fred*’) to type ‘class IBarAndBaz*’ (source type is not polymorphic) 

Obliger un casting ne travail, cependant:

doTest((IBarAndBaz*)pFred); 

mais je me demande comment ce coffre-fort et portable est (je développe pour Linux, Mac et Windows), et si cela fonctionne dans une situation réelle.


Enfin, je me rends compte ma méthode peut accepter un pointeur sur l'une des interfaces et dynamic_cast à l'autre (s) pour appliquer le type de paramètre correct lors de l'exécution, mais je préfère une solution de compilation.

+2

Ce n'est pas sûr et c'est portable. En fait, il est indéfini en C++. –

Répondre

6

Envisagez d'utiliser des solutions testées en premier - Boost.TypeTraits à la rescousse:

template<class T> 
void takeFooAndBar(const T& t) { 
    BOOST_STATIC_ASSERT(
      boost::is_base_of<IFoo, T>::value 
     && boost::is_base_of<IBar, T>::value); 
    /* ... */ 
} 
+1

Il ya un monde en dehors de boost :-) – mmmmmmmm

+0

Bien sûr, bcp aide avec ça;) Mais sérieusement, pourquoi dupliquer une telle collection complète de compilateurs-contournements, en particulier pour les caractères de type et similaires? –

+1

Je ne pense pas que ce soit une bonne idée de sortir le gros boost si le C++ normal (héritage virtuel) peut résoudre le problème. – mmmmmmmm

1

Vous pouvez obtenir l'effet en utilisant le modèle metaprogramming:

tempate<class C> 
void doTest(C* pObj) 
{ 
    pObj->bar(); 
    pObj->baz(); 
} 

se comporteront correctement pour les classes qui fournissent la barre() et baz(), et échoue à compiler pour les autres classes.

+0

C'est très intéressant. Mon cas réel est plus complexe, en fait - j'ai une classe qui reçoit l'objet qui doit implémenter les deux interfaces, garde un pointeur dessus, puis appelle ses méthodes. Je ne suis pas à l'aise pour implémenter cette grande classe en tant que modèle :( – ggambett

+0

Il est possible de coder une assertion à la compilation qu'un type fournit une fonction membre particulière sans invoquer cette fonction, ce que vous pouvez faire dans un wrapper modèle invoquant Fonction réelle, le code basé sur le modèle étant déclaré avec l'attribut inline, donc l'implémentation est compacte - il est plus facile d'utiliser celui de boost (voir la réponse de gf) que de lancer le vôtre – moonshadow

3

Pour ce faire, dans un style OO, vous avez besoin d'héritage virtuel pour faire en sorte que Fred se termine seulement avec une copie de IBar:

class IFooAndBar : public IFoo, public virtual IBar {}; 
class IBarAndQuux : public virtual IBar, public IQuux {}; 

class Fred : public IFooAndBar, public IBarAndQuux {}; 

Fred fred; 
fred.bar(); // unambiguous due to virtual inheritence 

Comme d'autres l'ont dit, vous pouvez faire quelque chose de similaire à votre deuxième tentative à l'aide modèles pour obtenir le polymorphisme statique.

La distribution que vous avez essayée n'est pas possible, car une instance de Fred n'est pas une instance de IBarAndBaz. La distribution forcée se compile car la plupart des conversions forcées seront compilées, que la conversion soit sûre ou non, mais dans ce cas, elle donnera un comportement indéfini. Alternativement, si vous ne voulez pas utiliser de modèles et que vous n'aimez pas l'explosion combinatoire de la définition de tous les groupes d'interfaces possibles, vous pouvez définir les fonctions pour prendre chaque interface comme paramètre séparé:

void doTest(IBar *bar, IBaz *baz) 
{ 
    bar->bar(); 
    baz->baz(); 
} 

class Fred : public IBar, public IBaz {}; 

Fred fred; 
doTest(&fred,&fred); 
+0

Cela pourrait marcher. que je devrais déclarer toutes les combinaisons possibles d'interfaces (au moins celles que j'utilise) dans la définition de Fred ... ce serait beaucoup mieux si cela pouvait être résolu à l'emplacement de la méthode utilisateur, pas à l'emplacement du paramètre, comme le les solutions basées sur un modèle font. – ggambett

1

ce que vous pouvez faire est de créer une classe avec un constructeur qui accepte un basé sur un modèle pointeur arbitraire, utilise downcasting implicite pour obtenir les deux interfaces que vous voulez, et met en œuvre puis l'interface combinée.

struct IFoo 
{ 
    virtual void foo() = 0; 
}; 

struct IBar 
{ 
    virtual void bar() = 0; 
}; 

struct IFooAndBar : public IFoo, public IBar {}; 

class FooAndBarCompositor : public IFooAndBar 
{ 
public: 
    template <class T> 
    FooAndBarCompositor(T* pImpl) : m_pFoo(pImpl), m_pBar(pImpl) {} 

    void foo() {m_pFoo->foo();} 
    void bar() {m_pBar->bar();} 

private: 
    IFoo* m_pFoo; 
    IBar* m_pBar; 
}; 

Ensuite, vous écrivez une fonction qui accepte IFooAndBar * si les deux interfaces sont nécessaires, et l'appelant peut construire un FooAndBarCompositor sur la pile qui envoie à l'objet de leur choix. Cela ressemble à:

void testFooAndBar(IFooAndBar* pI) {} 

void baz(Fred* pFred) 
{ 
    FooAndBarCompositor fb(pFred); 
    testFooAndBar(&fb); 
} 

Ceci n'est pas très général et vous oblige à écrire des fonctions de répartition dans le compositeur. Une autre approche est d'avoir un modèle de typographe d'interface générique:

template <class IA, class IB> 
class InterfaceCompositor 
{ 
public: 
    template <class T> 
    InterfaceCompositor(T* pObj) : m_pIA(pObj), m_pIB(pObj) {} 

    IA* AsA() const {return m_pIA;} 
    operator IA*() const {return AsA();} 
    IB* AsB() cosnt {return m_pIB;} 
    operator IB*() const {return AsB();} 

private: 
    IA* m_pIA; 
    IB* m_pIB; 
}; 

la fonction ressemble alors à:

void testFooAndBar(InterfaceCompositor<IFoo, IBar> pI) 
{ 
    IFoo* pFoo = pI; // Or pI.AsA(); 
    IBar* pBar = pI; // Of pI.AsB(); 
} 

Cela nécessite la fonction qui veut appliquer les multiples interfaces pour utiliser l'typographe où un A * ou B * est attendu (par exemple, paramètre d'affectation ou de fonction) ou appelle explicitement la méthode AsX() appropriée. Plus précisément, l'interface à utiliser ne peut pas être déduite de l'utilisation de l'opérateur -> et l'opérateur * n'a aucune signification sur le composite.

Si vous utilisez le code générique, vous pouvez utiliser le même modèle pour faire en sorte que l'objet prenne en charge IBar et IBaz. C++ 0x introduira des modèles variés qui permettront à ce concept d'être étendu à des nombres arbitraires de classes d'interface.