Nell'esempio, l'output è previsto. Virtual inheritance
entra in gioco nell'istanza quando si ha una classe con ereditarietà multiple le cui classi genitore ereditano anche dalla stessa classe/tipo (ad esempio "il problema dei diamanti"). Nel tuo esempio, le tue classi potrebbero essere configurate per ereditare virtualmente (se necessario altrove nel codice), ma non necessariamente 'virtualmente ereditare' in base al tuo esempio poiché nessuna delle classi derivate (B1/B2/C1/C2
) fa più che ereditare direttamente da A
.
ad espandersi, ho ottimizzato il vostro esempio per spiegare un po 'di più:
#include <cstdio>
#define tracefunc printf(__FUNCTION__); printf("\r\n")
struct A
{
A() { tracefunc; }
virtual void write() { tracefunc; }
virtual void read() { tracefunc; }
};
struct B1 : public A
{
B1() { tracefunc; };
void read(){ tracefunc; }
};
struct C1 : public A
{
C1() { tracefunc; };
void write(){ tracefunc; }
};
struct B2 : virtual public A
{
B2() { tracefunc; };
void read(){ tracefunc; }
};
struct C2 : virtual public A
{
C2() { tracefunc; };
void write(){ tracefunc; }
};
// Z1 inherits from B1 and C1, both of which inherit from A; when a call is made to any
// of the base function (i.e. A::read or A::write) from the derived class, the call is
// ambiguous since B1 and C1 both have a 'copy' (i.e. vtable) for the A parent class.
struct Z1 : public B1, public C1
{
Z1() { tracefunc; }
};
// Z2 inherits from B2 and C2, both of which inherit from A virtually; note that Z2 doesn't
// need to inherit virtually from B2 or C2. Since B2 and C2 both virtual inherit from A, when
// they are constructed, only 1 copy of the base A class is made and the vtable pointer info
// is "shared" between the 2 base objects (B2 and C2), and the calls are no longer ambiguous
struct Z2 : public B2, public C2
{
Z2() { tracefunc; }
};
int _tmain(int argc, _TCHAR* argv[])
{
// gets 2 "copies" of the 'A' base since 'B1' and 'C1' don't virtually inherit from 'A'
Z1 z1;
// gets only 1 "copy" of 'A' base since 'B2' and 'C2' virtualy inherit from 'A' and thus "share" the vtable pointer to the 'A' base
Z2 z2;
z1.write(); // ambiguous call to write (which one is it .. B1::write() (since B1 inherits from A) or A::write() ?)
z1.read(); // ambiguous call to read (which one is it .. C1::read() (since C1 inherits from A) or A::read() ?)
z2.write(); // not ambiguous: z2.write() calls C2::write() since it's "virtually mapped" to/from A::write()
z2.read(); // not ambiguous: z2.read() calls B2::read() since it's "virtually mapped" to/from A::read()
return 0;
}
Mentre potrebbe essere "ovvio" per noi esseri umani, che chiamiamo intendiamo fare nel caso della variabile z1
, poiché B1
non ha un metodo write
, mi aspetterei che il compilatore scelga il metodo C1::write
, ma a causa di come funziona la mappatura della memoria degli oggetti, presenta un problema poiché la copia di base di A
nell'oggetto C1
potrebbe avere informazioni diverse (puntatori/riferimenti/maniglie) rispetto alla copia della base A
nell'oggetto B1
ect (dal momento che ci sono tecnicamente 2 copie della base A
); quindi una chiamata a B1::read() { this->write(); }
potrebbe dare un comportamento inaspettato (anche se non indefinito).
La parola chiave virtual
su un identificatore di classe base rende esplicito che altre classi che ereditano virtualmente dallo stesso tipo di base, otterranno solo 1 copia del tipo di base.
Si noti che il codice sopra riportato non riesce a compilare con errori del compilatore che spiegano le chiamate ambigue per l'oggetto z1
. Se commentare la z1.write();
e z1.read();
linee l'uscita (almeno per me) è il seguente:
A::A
B1::B1
A::A
C1::C1
Z1::Z1
A::A
B2::B2
C2::C2
Z2::Z2
C2::write
B2::read
Nota le 2 chiamate al A
ctor (A::A
) prima Z1
è costruito, mentre Z2
ha solo 1 chiamata al costruttore A
.
Mi raccomando di leggere the following on virtual inheritance come si approfondisce su alcune delle altre insidie da prendere in considerazione (come il fatto che le classi virtualmente ereditate devono usare l'elenco di inizializzazione per effettuare chiamate di base class ctor, o che si dovrebbe evitare usare cast in stile C quando si esegue un tipo di ereditarietà).
Spiega anche un po 'di più a ciò a cui inizialmente si allude con l'ordinamento costruttore/distruttore e in particolare su come viene eseguito l'ordine quando si utilizza l'ereditarietà virtuale multipla.
Spero che possa aiutare a chiarire un po 'le cose.
Cosa ti aspettavi? "nonna" viene sempre chiamata per prima. Non importa che tipo di ereditarietà stai usando. Se era diverso, come potevano le classi di livello inferiore usare i dati dei genitori nei loro costruttori? – ixSci
L'ereditarietà virtuale fa la differenza solo in caso di ereditarietà multipla. Viene fornito per condividere alcune strutture tramite il grafico di ereditarietà. –