2013-01-22 15 views
6

Mam trudny czas z regułami wywoływania dla konstruktorów w hierarchii typów. Oto co mam zrobić:Hierarchia wywołań konstruktora

class A{ 
protected: 
    int _i; 
public: 
    A(){i = 0;} 
    A(int i) : _i(i){} 
    virtual ~A(){} 
    virtual void print(){std::cout<<i<<std::endl;} 
}; 

class B : virtual public A{ 
protected: 
    int _j; 
public: 
    B() : A(){_j = 0;} 
    B(int i, int j) : A(i), _j(j){} 
    virtual ~B(){} 
    virtual void print(){std::cout<<i<<", "<<j<<std::endl;} 
}; 

class C : virtual public B{ 
protected: 
    int _k; 
public: 
    C() : B(){_k = 0;} 
    C(int i, int j, int k} : B(i,j), _k(k){} 
    virtual ~C(){} 
    virtual void print(){std::cout<<i<<", "<<j<<", "<<k<<std::endl;} 
}; 

int main(){ 
    C* myC = new C(1,2,3); 
    myC->print(); 
    delete myC; 
    return 0; 
} 

Teraz chciałbym mieć nową C (1,2,3) wywołanie konstruktora z B (1,2), które potem z kolei powinno wywołać konstruktor A (1) do przechowywania _i = 1, _j = 2, _k = 3. Podczas tworzenia instancji myC klasy C, z jakiegoś powodu nie rozumiem, pierwszy konstruktor, który ma zostać wywołany, jest standardowym konstruktorem A, tj. A :: A(); To oczywiście prowadzi do błędnych wyników, ponieważ zabezpieczonej zmiennej _i przypisuje się wartość 0. Konstruktor A (1) nigdy nie jest wywoływany. Dlaczego tak jest? Uważam to za bardzo sprzeczne z intuicją. Czy istnieje sposób uniknięcia jawnego wywoływania wszystkich konstruktorów w hierarchii typów w celu osiągnięcia pożądanego zachowania?

Thx za pomoc!

+0

Thx za miłe odpowiedzi. Myślę więc, że wrócę do Stroustrup, aby ponownie przeczytać koncepcję wirtualnego dziedziczenia. Wygląda na to, że nie jest rozsądnie używać go domyślnie;) – user1999920

+0

Wiele osób zastanawia się, dlaczego domyślnie dziedziczenie nie jest wirtualne. Cóż, sam znalazłeś odpowiedź :) – Gorpik

Odpowiedz

5

Podczas korzystania z wirtualnego dziedziczenie, klasy najbardziej pochodzić musi wywołać konstruktorów dla wszystkich wirtualnych baz bezpośrednio. W takim przypadku konstruktor dla C musi wywołać konstruktorów dla B i A. Ponieważ wywołujesz tylko konstruktor B, używa on domyślnego konstruktora A. Nie ma znaczenia, że ​​konstruktor B wywołuje innego konstruktora A: ponieważ jest to wirtualna klasa podstawowa, to wywołanie jest ignorowane.

Masz dwa sposoby obejścia tego problemu: jawnie wywołać konstruktora A(int):

C(int i, int j, int k} : A (i), B(i,j), _k(k){} 

lub użyj normalnego dziedziczenia zamiast wirtualny.

+1

Najbardziej wyprowadzona klasa nie _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ jawnie nie można jawnie wywoływać konstruktorów dla swoich wirtualnych klas bazowych, może pozwolić im być domyślnie zainicjalizowane jak w pytaniu. Jednak robi to, ale masz rację, że jest to zawsze najbardziej pochodna klasa, która zainicjowała wirtualne bazy. –

+0

@CharlesBailey Odzyskuję moją odpowiedź, masz rację: nie wyjaśniłem tego poprawnie (że * wyraźnie * wprowadza w błąd). Naprawiam to. – Gorpik

6

Jest tak dlatego, że korzystasz z wirtualnego dziedziczenia, co ma sens tylko w przypadku wielu dziedziczeń. Po prostu dziedzicz normalnie, a wszystko będzie takie, jak tego oczekujesz.

+0

Jeśli 'A' jest interfejsem, a' B' rozszerza ten interfejs, to wirtualne dziedziczenie jest naprawdę jedynym właściwym rozwiązaniem. (Oczywiście, jeśli "A" jest interfejsem, nie będzie miał żadnych elementów danych, a więc nie będzie żadnych zdefiniowanych przez użytkownika konstruktorów. Ogólnie dziedziczenie wirtualne ma zastosowanie tylko do interfejsów.) –

+0

@JamesKanze, to jest złe, nie musisz potrzebujesz dziedziczenia wirtualnego, jeśli nie tworzysz dziedziczenia z klas 2+ posiadających wspólny przodek. Ponadto można wdrożyć wiele interfejsów bez dziedziczenia wirtualnego: patrz na przykład COM. – Steed

+0

Myślę, że nie dostaję twojej definicji "interfejsu". Masz na myśli interfejs typu Java (czyli abstrakcyjną klasę bez członków)? Jednakże widziałem wiele różnych rzeczy w C++, które można nazwać wewnętrznymi, ale żaden z nich nie potrzebował wirtualnego dziedziczenia. Oczywiście, w pewnych okolicznościach klasy abstrakcyjne mogą zostać uwikłane w wiele wykresów dziedziczenia, ale skoro nie mają członków, to czy vtables po prostu nie działają, nawet bez dziedziczenia wirtualnego? –

7

Czy naprawdę potrzebujesz tutaj dziedziczenia virtual? masz problem, ponieważ pierwszy wirtualny ctor bazy zostanie wywołany jako pierwszy, ale nie określisz żadnego po odziedziczeniu C z B (ostatnie z nich ma już A wirtualnie odziedziczony, więc został wywołany domyślny).

Jednym z rozwiązań jest usunięcie dziedziczenia wirtualnego ... jak wspomniano w odpowiedzi Arne Mertza. innego (jeśli naprawdę chcesz wirtualnego dziedziczenie) jest wywołanie A wyraźnie od C ctor:

C(int i, int j, int k} : A(i), B(i,j), _k(k){} 
0

Dlaczego deklarujesz dziedziczenie wirtualne? Jeśli usuniesz wirtualne słowo kluczowe z klasy B: wirtualne publiczne A {... wtedy twój kod będzie działał poprawnie. Deklarując wirtualny A, C wywoła bezpośrednio A(). Jeśli usuniesz wirtualne, C nie zadzwoni A().