2009-11-12 3 views
13

J'ai la structure de classe suivante:héritage Interface en C++

class InterfaceA 
{ 
    virtual void methodA =0; 
} 

class ClassA : public InterfaceA 
{ 
    void methodA(); 
} 

class InterfaceB : public InterfaceA 
{ 
    virtual void methodB =0; 
} 

class ClassAB : public ClassA, public InterfaceB 
{ 
    void methodB(); 
} 

Maintenant, le code suivant n'est pas compilable:

int main() 
{ 
    InterfaceB* test = new ClassAB(); 
    test->methodA(); 
} 

Le compilateur dit que la méthode methodA() est virtuelle et non mis en œuvre. Je pensais qu'il est mis en œuvre dans ClassA (qui implémente le InterfaceA). Est-ce que quelqu'un sait où est ma faute?

Répondre

19

C'est parce que vous avez deux copies de InterfaceA. Voir ceci pour une plus grande explication: https://isocpp.org/wiki/faq/multiple-inheritance (votre situation est semblable à «le diamant redouté»).

Vous devez ajouter le mot-clé virtual lorsque vous héritez ClassA d'InterfaceA. Vous devez également ajouter virtual lorsque vous héritez InterfaceB d'InterfaceA.

+0

Merci pour la clarification laura. –

+0

Je pensais qu'une fois qu'une fonction est déclarée 'virtual', elle est toujours virtuelle dans toute la hiérarchie des classes, que les classes dérivées utilisent effectivement' virtual' ou non lors de la définition – johnbakers

4

Ce problème existe parce que C++ n'a pas vraiment d'interfaces, seulement des classes virtuelles pures avec héritage multiple. Le compilateur ne sait pas où trouver l'implémentation de methodA() car il est implémenté par une classe de base différente de ClassAB. Vous pouvez contourner ce problème en mettant en place methodA() dans ClassAB() pour appeler la mise en œuvre de base:

class ClassAB : public ClassA, public InterfaceB 
{ 
    void methodA() 
    { 
     ClassA::methodA(); 
    } 

    void methodB(); 
} 
+0

Si les règles de C++ sont similaires à celles de Java, le compilateur n'en serait toujours pas informé parce que le pointeur a été déclaré comme 'InterfaceB', qui n'a pas' methodA'. –

+0

mmyers: C'est faux pour C++ et Java. InterfaceB hérite d'InterfaceA où methodA est défini. – jmucchiello

+0

InterfaceB hérite de l'interfaceA donc il devrait avoir la méthode – stonemetal

2

Vous avez un diamant redouté ici. InterfaceB et ClassA doivent hériter virtuellement d'InterfaceA Sinon, vous avez deux copies de MethodA dont l'une est encore virtuelle pure. Vous ne devriez pas être capable d'instancier cette classe. Et même si vous étiez - compilateur ne serait pas en mesure de décider quelle méthode A appeler.

9

L'héritage virtuel, que Laura a suggéré, est, bien sûr, la solution du problème. Mais il ne finit pas avec une seule interfaceA. Il a aussi des "effets secondaires", par ex. voir https://isocpp.org/wiki/faq/multiple-inheritance#mi-delegate-to-sister. Mais si vous vous y êtes habitué, cela pourrait vous être utile.

Si vous ne voulez pas d'effets secondaires, vous pouvez utiliser le modèle:

struct InterfaceA 
{ 
    virtual void methodA() = 0; 
}; 

template<class IA> 
struct ClassA : public IA //IA is expected to extend InterfaceA 
{ 
    void methodA() { 5+1;} 
}; 

struct InterfaceB : public InterfaceA 
{ 
    virtual void methodB() = 0; 
}; 

struct ClassAB 
    : public ClassA<InterfaceB> 
{ 
    void methodB() {} 
}; 

int main() 
{ 
    InterfaceB* test = new ClassAB(); 
    test->methodA(); 
} 

Ainsi, nous ayant exactement une classe parente.

Mais il semble plus laid quand il y a plus d'une classe "partagée" (InterfaceA est "partagé", parce qu'il est au-dessus de "diamant redouté", voir ici https://isocpp.org/wiki/faq/multiple-inheritance tel que posté par Laura). Voir par exemple (ce qui sera, si ClassA implémente InterfaceC aussi):

struct InterfaceC 
{ 
    virtual void methodC() = 0; 
}; 

struct InterfaceD : public InterfaceC 
{ 
    virtual void methodD() = 0; 
}; 

template<class IA, class IC> 
struct ClassA 
    : public IA //IA is expected to extend InterfaceA 
    , public IC //IC is expected to extend InterfaceC 
{ 
    void methodA() { 5+1;} 
    void methodC() { 1+2; } 
}; 

struct InterfaceB : public InterfaceA 
{ 
    virtual void methodB() = 0; 
}; 

struct ClassAB 
    : public ClassA<InterfaceB, InterfaceC> //we had to modify existing ClassAB! 
{ 
    void methodB() {} 
}; 

struct ClassBD //new class, which needs ClassA to implement InterfaceD partially 
    : public ClassA<InterfaceB, InterfaceD> 
{ 
    void methodB() {} 
    void methodD() {} 
}; 

La mauvaise chose, que vous aviez besoin de modifier ClassAB existant. Mais vous pouvez écrire:

template<class IA, class IC = interfaceC> 
struct ClassA 

Puis ClassAB reste inchangé:

struct ClassAB 
     : public ClassA<InterfaceB> 

Et vous avez implémentation par défaut pour le paramètre de modèle IC.

De quelle manière vous pouvez décider. Je préfère le modèle, quand c'est simple à comprendre.Il est assez difficile d'entrer dans l'habitude, que B :: incrementAndPrint() et C :: incrementAndPrint() affichera différentes valeurs (pas votre exemple), voir ceci:

class A 
{ 
public: 
    void incrementAndPrint() { cout<<"A have "<<n<<endl; ++n; } 

    A() : n(0) {} 
private: 
    int n; 
}; 

class B 
    : public virtual A 
{}; 

class C 
    : public virtual A 
{}; 

class D 
    : public B 
    : public C 
{ 
public: 
    void printContents() 
    { 
    B::incrementAndPrint(); 
    C::incrementAndPrint(); 
    } 
}; 

int main() 
{ 
    D d; 
    d.printContents(); 
} 

Et la sortie:

A have 0 
A have 1