2009-11-12 12 views
13

Mam następujące klasy strukturę:Interfejs Dziedziczenie w 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(); 
} 

Teraz następujący kod nie jest compilable:

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

Kompilator mówi, że metoda methodA() jest wirtualny i nie realizowane. Myślałem, że jest on zaimplementowany w ClassA (który implementuje InterfaceA). Czy ktoś wie, gdzie moja wina?

Odpowiedz

19

to dlatego, że masz dwie kopie InterfaceA. Zobacz to dla większego wyjaśnienia: https://isocpp.org/wiki/faq/multiple-inheritance (Twoja sytuacja jest podobna do "diamentu budzącego lęk").

Musisz dodać słowo kluczowe virtual, gdy odziedziczysz ClassA z InterfaceA. Musisz również dodać virtual, gdy dziedziczysz InterfaceB z InterfaceA.

+0

Dzięki za wyjaśnienie Laura. –

+0

Myślałem, że gdy funkcja zostanie zadeklarowana, 'virtual' jest zawsze wirtualna w hierarchii klas, niezależnie czy klasy pochodne używają' wirtualnego' lub nie, podczas definiowania – johnbakers

4

Ten problem występuje, ponieważ C++ nie ma naprawdę interfejsów, a jedynie czyste wirtualne klasy z wieloma dziedziczeniami. Kompilator nie wie, gdzie znaleźć implementację methodA(), ponieważ jest implementowany przez inną klasę bazową ClassAB. Można obejść ten problem poprzez wdrożenie methodA() w ClassAB() zadzwonić realizację podstawy:

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

    void methodB(); 
} 
+0

Jeśli reguły C++ są podobne do Javy, kompilator wciąż o tym nie wiedział, ponieważ wskaźnik został zadeklarowany jako "InterfaceB", który nie ma "metodyA". –

+0

mmyers: To źle zarówno dla C++, jak i Java. InterfaceB dziedziczy po interfejsie InterfaceA, gdzie zdefiniowano metodę A. – jmucchiello

+0

InterfaceB dziedziczy po interfejsie A, więc powinien mieć metodęA – stonemetal

2

Masz tutaj przerażający diament. InterfaceB i ClassA muszą wirtualnie dziedziczyć po InterfaceA W przeciwnym razie ClassAB ma dwie kopie MethodA, z których jedna nadal jest czystą wirtualną. Nie powinieneś być w stanie utworzyć instancji tej klasy. A nawet gdybyś był - kompilator nie byłby w stanie zdecydować, którą metodę AA wywołać.

9

Dziedziczenie wirtualne, które sugerowała Laura, jest oczywiście rozwiązaniem problemu. Ale nie kończy się to na posiadaniu tylko jednego InterfaceA. Ma również "skutki uboczne", np. patrz https://isocpp.org/wiki/faq/multiple-inheritance#mi-delegate-to-sister. Ale jeśli się do tego przyzwyczaisz, może się przydać.

Jeśli nie chcesz skutków ubocznych, można użyć szablonu:

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(); 
} 

Więc mają dokładnie jedną klasę nadrzędną.

Ale wygląda bardziej brzydko, gdy istnieje więcej niż jedna klasa "udostępniona" (interfejs A jest "udostępniony", ponieważ znajduje się na wierzchu "przerażającego diamentu", patrz tutaj https://isocpp.org/wiki/faq/multiple-inheritance opublikowany przez Laurę). Zobacz przykład (co będzie, jeśli ClassA realizuje interfaceC zbyt):

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() {} 
}; 

złą rzeczą, że trzeba zmodyfikować istniejącą ClassAB. Ale można napisać:

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

Potem pozostaje niezmieniona ClassAB:

struct ClassAB 
     : public ClassA<InterfaceB> 

I masz domyślną implementację dla parametru szablonu IC.

Którą drogą należy wybrać. Preferuję szablon, kiedy jest prosty do zrozumienia.Jest to dość trudne do uzyskania w zwyczaju, że B :: incrementAndPrint() i C :: incrementAndPrint() będzie drukować różne wartości (nie swoim przykładzie), patrz poniżej:

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(); 
} 

a wyjście:

A have 0 
A have 1