18

Nel seguente codice, sembra che la classe C non abbia accesso al costruttore di A, che è richiesto a causa dell'ereditarietà virtuale. Tuttavia, il codice viene ancora compilato e eseguito. Perché funziona?Problema ereditario virtuale privato C++

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

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

Inoltre, se rimuovo il costruttore predefinito da A, ad es.

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

poi

class C: public B {}; 

sarebbe (inaspettatamente) compilare, ma

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

non sarebbe compilare, come previsto.

Codice compilato con "g ++ (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)", ma è stato verificato che si comporta allo stesso modo anche con altri compilatori.

+0

Con g ++ 4.4 viene compilato. Mentre non sono stato in grado di trovare un riferimento autorevole, il mio credo è che dovrebbe essere compilato. La 'classe' più derivata può costruire il sottooggetto di tipo' A'. Si noti che esistono implementazioni per sigillare l'ereditarietà in base alla combinazione dell'eredità 'privata virtuale' * insieme * con un costruttore privato in' A' e accesso concesso a 'B' attraverso l'amicizia. Tutta la complicazione non sarebbe necessaria se fosse sufficiente utilizzare l'ereditarietà privata virtuale. –

+0

@ DavidRodríguez-dribeas "_Tutte le complicazioni non sarebbero necessarie se fosse sufficiente l'utilizzo dell'eredità virtuale privata." "Nessuno qui ha affermato che l'idioma di suggellamento funziona senza un amministratore privato. Nell'idioma di suggellamento, l'ereditarietà privata non è necessaria, ma è necessaria per rendere l'uso dell'idioma un dettaglio di implementazione. – curiousguy

risposta

13

In base alla classe C++ Core Issue #7 non è possibile derivare una base privata virtuale. Questo è un bug nel compilatore.

+0

Tranne che g ++ e como non si lamentano dell'esempio del problema principale. Questo è abbastanza per farmi dubitare della mia risposta, ma mi piacerebbe un riferimento più recente di uno dei vecchi problemi che potrebbe benissimo non essere stato aggiornato se le regole sono cambiate. – AProgrammer

+0

In effetti, questo problema è stato chiuso, il che significa che non è un problema. –

+0

"Non è possibile derivare _class con una base privata virtuale da" "Wrong. – curiousguy

2

Le classi di base virtuali vengono sempre inizializzate dalle classi più derivate (C qui). Il compilatore deve verificare che il costruttore è accessibile (cioè ricevo un errore con g ++ 3.4 per

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

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

mentre descrizione implica non v'è nessuno), ma il fatto che come base, A è privata o no doesn' t importa (sovvertire sarebbe facile: class C: public B, private virtual A).

Il motivo per cui i costruttori delle classi di base virtuali vengono richiamati dalla classe più derivata è che deve essere creato prima che qualsiasi classe li abbia come classe base.

Modifica: Kirill ha menzionato un vecchio problema di base che è in contrasto con la mia lettura e il comportamento dei compilatori recenti. Cercherò di ottenere riferimenti standard in un modo o nell'altro, ma ciò può richiedere tempo.

+0

Ovviamente hai ragione. – curiousguy

+0

Dovresti dire che stai solo affrontando la seconda domanda e non la prima. – ndkrempel

6

Per la seconda domanda, è probabilmente perché non si causa implicitamente definito. Se il costruttore viene semplicemente dichiarato implicitamente, non vi è alcun errore. Esempio:

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; 

Per la prima domanda, dipende dal nome del compilatore. Non ho idea di quello che la norma specifica, ma questo codice per esempio è corretto perché il nome della classe esterna (al posto del nome classe ereditata) è accessibile:

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

Forse lo standard è underspecified a questo punto. Dovremo guardare.


Non sembra esserci alcun problema con il codice. Inoltre, vi è indicazione che il codice è valido. Il subobject di classe base (virtuale) viene inizializzato di default - non esiste un testo che implichi che la ricerca del nome per il nome della classe sia dine all'interno dello scope di C.Ecco cosa dice la norma:

12.6.2/8 (C++ 0x)

Se un dato membro di dati non statico o classe di base non è nominato da un mem-inizializzatore-id (compreso il caso dove non c'è mem-inizializzatore-lista, perché il costruttore non ha ctor-inizializzatore) e l'entità non è una classe base virtuale di una classe astratta

[...] in caso contrario, l'entità è default-inizializzato

E C++ 03 ha un testo simile (meno testo è chiaro - dice semplicemente che il suo costruttore predefinito è chiamato in un posto, e in un altro lo fa dipendere dal fatto che la classe sia un POD). Affinché il compilatore inizializzi di default il subobject, deve semplicemente chiamare il suo costruttore di default - non è necessario prima cercare il nome della classe base (lo conosce già quale base è considerata).

Considerate questo codice che certamente è destinato ad essere valida, ma che avrebbe fallire se questo sarebbe essere fatto (vedi 12.6.2/4 in C++ 0x)

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

Se costruttore di default del compilatore avrebbe semplicemente guardare -up nome classe A all'interno di C, avrebbe un risultato di ricerca ambiguo in relazione a ciò che subobject deve inizializzare, poiché vengono trovati sia i nomi di classe non virtuali A sia quelli virtuali A. Se il tuo codice è destinato a essere mal formato, direi che lo standard ha certamente bisogno di essere chiarito.


per il costruttore, notare cosa 12.4/6 dice circa il distruttore di C:

Tutti i distruttori sono chiamati come se fossero riferimento con un nome qualificato, cioè, ignorando eventuali distruttori imperativi virtuali in più classi derivate.

Questo può essere interpretato in due modi:

  • chiamare A :: ~ A()
  • chiamando :: :: A ~ A()

Sembra io che lo standard è meno chiaro qui. Il secondo modo lo renderebbe valido (da 3.4.3/6, C++ 0x, poiché entrambi i nomi di classe A vengono cercati in ambito globale), mentre il primo lo renderà non valido (perché entrambi i nomi di classe ereditata saranno entrambi A). Dipende anche da ciò subobject della ricerca inizia (e credo che dovremo usare la classe base virtuale 'subobject come punto di partenza). Se questo va come

virtual_base -> A::~A(); 

Poi ci sarà direttamente trovare il 'nome della classe come un nome pubblico, perché non dovremo passare attraverso i derivati ​​della classe base virtuale ambiti e trovare il nome come non accessibili. Di nuovo, il ragionamento è simile.Considerare:

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

Se il distruttore sarebbe semplicemente chiamare this->A::~A(), questo invito non sarebbe valida a causa del risultato di ricerca ambiguo A come un nome di classe ereditata (non si può fare riferimento a qualsiasi membro-funzione non-statico del oggetto classe base diretta dall'ambito C, vedere 10.1/3, C++ 03). Dovrà identificare in modo univoco i nomi delle classi che sono coinvolti e deve iniziare con la classe 'riferimento subobject come a_subobject->::A::~A();.

+1

"_it dipende dal nome del compilatore." "??? Nessun nome è "usato" e non vi è alcun problema di ricerca del nome. ** I costruttori sono chiamati. ** Faranno meglio a essere accessibili. – curiousguy

+0

@curious il tuo commento non ha senso per me. Stai chiedendo che cosa "dipende dal nome del compilatore". Perché stai facendo "I costruttori sono chiamati". grassetto? FWIW, il controllo dell'accesso viene effettuato sui nomi, non su qualsiasi altra cosa. Inoltre è fatto su con/destructors ma quello è qualcosa che non è descritto bene nello standard. Non hai letto la mia risposta completa sembra. Cita "Tutti i distruttori sono chiamati come se fossero referenziati con un nome qualificato" - "nessun problema di ricerca del nome"? Divertente. È necessario essere più chiari sui propri dubbi ... –

+0

"_FWIW, il controllo dell'accesso viene effettuato sui nomi, _" No. Il controllo dell'accesso viene eseguito sulla dichiarazione utilizzata. La dichiarazione non è accessibile qui. "Si fa anche su con/destructors ma è qualcosa che non è ben descritto nello Standard." "Molte cose sono ovvie. "_Credita" Tutti i distruttori sono chiamati come se fossero referenziati con un nome qualificato "-" nessun problema di ricerca del nome "? _" Quale problema di ricerca del nome? – curiousguy