18

W poniższym kodzie wydaje się, że klasa C nie ma dostępu do konstruktora A, który jest wymagany z powodu wirtualnego dziedziczenia. Jednak kod nadal się kompiluje i działa. Dlaczego to działa?C++ prywatny problem dziedziczenia wirtualnego

class A {}; 
class B: private virtual A {}; 
class C: public B {}; 

int main() { 
    C c; 
    return 0; 
} 

Co więcej, jeśli usunę domyślny konstruktor z A, np.

class A { 
public: 
    A(int) {} 
}; 
class B: private virtual A { 
public: 
    B() : A(3) {} 
}; 

następnie

class C: public B {}; 

że (niespodziewanie) sporządza, ale

class C: public B { 
public: 
    C() {} 
}; 

nie opracowania, zgodnie z oczekiwaniami.

Kod skompilowany za pomocą "g ++ (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)", ale został sprawdzony, aby zachowywać się tak samo z innymi kompilatorami.

+0

Z g ++ 4.4 kompiluje. Chociaż nie byłem w stanie znaleźć autorytatywnego odniesienia, moim zdaniem jest to, że powinien się skompilować. Najbardziej wyprowadzona klasa 'C' może skonstruować podobiekt typu" A ". Zauważ, że istnieją implementacje do zamykania dziedziczenia oparte na połączeniu dziedziczenia 'private virtual' * razem * z prywatnym konstruktorem w' A' i dostępie przyznawanym 'B' przez przyjaźń. Wszystkie komplikacje byłyby niepotrzebne, gdyby wystarczyło tylko prywatne dziedziczenie wirtualne. –

+0

@ DavidRodríguez-dribeas "_ Wszystkie komplikacje byłyby niepotrzebne, gdyby wystarczyło tylko prywatne dziedziczenie wirtualne." "Nikt tutaj nie twierdził, że idiom pieczęci działa bez prywatnego ctor. W idiomie uszczelniającym prywatne dziedziczenie nie jest potrzebne, ale jest potrzebne, aby użycie idiomu było szczegółem implementacji. – curiousguy

Odpowiedz

13

Według klasy C++ Core Issue #7 nie można uzyskać klasy z wirtualną bazą prywatną. To jest błąd w kompilatorze.

+0

Z wyjątkiem tego, że g ++ i como nie narzekają na przykład w głównym numerze. To wystarcza, aby wzbudzić wątpliwości co do mojej odpowiedzi, ale chciałbym uzyskać bardziej aktualne odniesienie niż jeden ze starszych problemów, które nie mogły zostać zaktualizowane, gdyby zasady się zmieniły. – AProgrammer

+0

W rzeczywistości ta kwestia została zamknięta, co oznacza, że ​​nie stanowi problemu. –

+0

"Klasy _ z wirtualną bazą prywatną nie można wyprowadzić from_" Wrong. – curiousguy

2

Wirtualne klasy bazowe są zawsze inicjalizowane z najczęściej wyprowadzonych klas (tutaj C). Kompilator musi sprawdzić, że konstruktor jest dostępna (tzn pojawia się błąd z g ++ 3.4

class A { public: A(int) {} }; 
class B: private virtual A {public: B() : A(0) {} }; 
class C: public B {}; 

int main() { 
    C c; 
    return 0; 
} 

natomiast Twój opis sugeruje, nie ma nikogo), ale fakt, że jako bazę, jest prywatny, czy nie robi” nieważne (do obalenia byłoby łatwo: class C: public B, private virtual A).

Powodem, dla którego konstruktory klas wirtualnych są wywoływane z najbardziej pochodnej klasy, jest to, że należy ją skonstruować przed klasami mającymi je jako klasę podstawową.

Edytuj: Kirill wspomniał o starym kluczowym problemie, który jest dziwny w moim odczycie i zachowaniu się najnowszych kompilatorów. Spróbuję uzyskać standardowe referencje w taki czy inny sposób, ale to może zająć trochę czasu.

+0

Masz oczywiście rację. – curiousguy

+0

Powinieneś powiedzieć, że adresujesz tylko drugie pytanie, a nie pierwsze. – ndkrempel

6

Dla drugiego pytania prawdopodobnie wynika to z tego, że nie jest ono domyślnie zdefiniowane jako . Jeśli konstruktor jest tylko domyślnie zadeklarowany, nie ma błędu. Przykład:

struct A { A(int); }; 
struct B : A { }; 
// goes fine up to here 

// not anymore: default constructor now is implicitly defined 
// (because it's used) 
B b; 

Na pierwsze pytanie - zależy to od nazwy używanej przez kompilator. Nie mam pojęcia, co średnia określa, ale ten kod jest prawidłowy, na przykład dlatego, że zewnętrzna nazwa klasy (zamiast odziedziczyła nazwę klasy) jest dostępny:

class A {}; 
class B: private virtual A {}; 
class C: public B { C(): ::A() { } }; // don't use B::A 

Może standard jest underspecified w tym momencie. Będziemy musieli patrzeć.


Nie ma problemu z kodem. Ponadto istnieje wskazówka, że ​​kod jest ważny. Obiekt podrzędny (wirtualny) jest domyślnie zainicjowany - nie ma tekstu, który sugeruje, że wyszukiwanie nazwy dla nazwy klasy odbywa się wewnątrz zakresu C.Oto co mówi standardowe:

12.6.2/8 (C++ 0x)

Jeśli dana niestatyczny członek danych lub klasa bazowa nie jest nazwany przez MEM-inicjatora-id (w tym przypadku gdzie nie ma mem-inicjator-lista, ponieważ konstruktor ma konstruktor-inicjator), a jednostka nie jest wirtualna klasa podstawowa klasy abstrakcyjnej

[...] inaczej, podmiot domyślny przygotowanej

A C++ 03 ma podobny tekst (mniej czytelny tekst - po prostu mówi, że jego domyślny konstruktor jest wywoływany w jednym miejscu, a inny uzależnia go od tego, czy klasa jest POD). Aby kompilator domyślnie zainicjował obiekt podrzędny, wystarczy wywołać jego domyślny konstruktor - nie ma potrzeby sprawdzania najpierw nazwy klasy bazowej (to już wie, jaka baza jest uważana).

Rozważmy następujący kod, który na pewno ma być ważne, ale to nie powieść, jeśli to byłoby być zrobione (patrz 12.6.2/4 w C++ 0x)

struct A { }; 
struct B : virtual A { }; 
struct C : B, A { }; 
C c; 

Jeśli konstruktor domyślny kompilator byłaby po prostu patrzeć -up class name A wewnątrz C, miałoby to niejednoznaczny wynik wyszukiwania w odniesieniu do tego obiektu podrzędnego do zainicjowania, ponieważ znaleziono nie-wirtualne nazwy klas A i wirtualne A. Jeśli twój kod ma być źle sformułowany, powiedziałbym, że Standard musi zostać wyjaśniony.


Dla konstruktora, zauważyć co 12.4/6 mówi o destruktora C:

Wszystkie destruktory nazywane są tak, jakby były odniesione z kwalifikowanej nazwy, czyli pomijając ewentualne destruktory wirtualne nadrzędne w więcej klasach pochodnych.

ten można interpretować na dwa sposoby:

  • wywołanie :: ~ A()
  • nazywając :: A :: ~ A()

Wydaje mnie, że Standard jest tu mniej jasny. Drugi sposób sprawiłby, że byłby ważny (przez 3.4.3/6, C++ 0x, ponieważ obie nazwy klasy A są wyszukiwane w zasięgu globalnym), podczas gdy pierwsza sprawi, że będzie on nieważny (ponieważ zarówno A znajdzie odziedziczone nazwy klas). Zależy to również od tego, pod jakim pojęciem zaczyna się wyszukiwanie (i uważam, że będziemy musieli użyć wirtualnego obiektu bazowego "podobiekt" jako punktu początkowego). Jeśli to idzie jak

virtual_base -> A::~A(); 

Wtedy będziemy bezpośrednio znaleźć wirtualną „nazwę klasy jako nazwa publiczną, ponieważ nie będzie musiał przejść przez pochodzących klasa baza zakresów i znaleźć nazwę jako niedostępne. Znowu rozumowanie jest podobne.Rozważmy:

struct A { }; 
struct B : A { }; 
struct C : B, A { 
} c; 

Jeśli destruktor po prostu zadzwonić this->A::~A(), to połączenie nie będzie ważny z powodu niejednoznacznego wyniku przeglądowej z A jako dziedziczną nazwy klasy (nie można odnosić się do każdej niestatyczny członków funkcji z bezpośredni obiekt klasy bazowej z zakresu C, patrz 10.1/3, C++ 03). Będzie musiał jednoznacznie identyfikować nazwy klas, które są zaangażowane, i musi zaczynać się od referencji podobiektowej klasy, takiej jak a_subobject->::A::~A();.

+1

"_it zależy od nazwy używanej przez kompilator." Żadne imię nie jest "używane" i nie ma problemu z wyszukiwaniem nazwiska. ** Wywoływane są konstruktory. ** Będą lepiej dostępne. – curiousguy

+0

@curious Twój komentarz nie ma dla mnie sensu. Pytasz, co "" to zależy od nazwy, której używa kompilator. "" Oznacza? Dlaczego robisz "Konstruktorzy są nazywani". pogrubienie? FWIW, sprawdzanie dostępu odbywa się na nazwach, a nie na niczym innym. Robi się to również na con/destructor, ale jest to coś, co nie jest dobrze opisane w Standardzie. Nie przeczytałeś mojej kompletnej odpowiedzi, jak się wydaje. Cytuje "Wszystkie destruktory są wywoływane tak, jakby były odwoływane z kwalifikowaną nazwą" - "bez problemu z wyszukiwaniem nazw"? Zabawny. Musisz być bardziej zrozumiały o swoich obawach ... –

+0

"_FWIW, sprawdzanie dostępu odbywa się na nazwach, _" Nie. Kontrola dostępu odbywa się na deklaracji. Deklaracja nie jest dostępna tutaj. "Robi się to również na con/destruktorze, ale jest to coś, co nie jest dobrze opisane w Standard._" Wiele rzeczy jest oczywiste. "_To cytuje" Wszystkie destruktory są wywoływane tak, jakby były przywoływane z kwalifikowaną nazwą "-" bez problemu z wyszukiwaniem nazw "? _" Które wydanie wyszukiwania nazwy? – curiousguy