Quanti vptr sono in genere necessari per un oggetto il cui clas (figlio) ha un'ereditarietà singola con una classe base che eredita multiple1 e base2. Qual è la strategia per identificare quanti vptrs ha fornito un oggetto ha una coppia di ereditarietà singola e ereditarietà multipla. Anche se lo standard non specifica su vptrs, ma voglio solo sapere come un'implementazione implementa la funzione virtuale.Quanti vptr avranno un oggetto di classe (utilizza l'ereditarietà singola/multipla)?
risposta
Perché ti importa? La risposta semplice è abbastanza, ma suppongo che tu voglia qualcosa di più completo.
Questo non fa parte dello standard, quindi qualsiasi implementazione è libera di fare come desiderano, ma una regola generale è che in un'implementazione che utilizza i puntatori di tabella virtuali, come un'approssimazione di zero, per l'invio dinamico si è necessario al massimo tanti puntatori alle tabelle virtuali in quanto esistono classi che aggiungono un nuovo metodo virtuale alla gerarchia. (In alcuni casi la tabella virtuale può essere esteso, e la base e tipi derivati condividere una singola vptr
)
// some examples:
struct a { void foo(); }; // no need for virtual table
struct b : a { virtual foo1(); }; // need vtable, and vptr
struct c : b { void bar(); }; // no extra virtual table, 1 vptr (b) suffices
struct d : b { virtual bar(); }; // extra vtable, need b.vptr and d.vptr
struct e : d, b {}; // 3 vptr, 2 for the d subobject and one for
// the additional b
struct f : virtual b {};
struct g : virtual b {};
struct h : f, g {}; // single vptr, only b needs vtable and
// there is a single b
Fondamentalmente ciascuna sotto-oggetto di un tipo che richiede una propria distribuzione dinamica (non può riutilizzare direttamente i genitori) avrebbe bisogno la propria tabella virtuale e vptr.
In realtà i compilatori uniscono diversi vtables in un singolo vtable. Quando d
aggiunge una nuova funzione virtuale sul set di funzioni in b
, il compilatore unirà le potenziali due tabelle in una singola aggiungendo i nuovi slot alla fine del vtable, quindi il vtable per d
sarà una versione estesa di il vtable per b
con elementi extra alla fine mantenendo la compatibilità binaria (ovvero il vtable d
può essere interpretato come un vtable b
per accedere ai metodi disponibili in b
) e l'oggetto d
avrà un singolo vptr
.
Nel caso di ereditarietà multipla le cose diventano un po 'più complicate in quanto ogni base deve avere lo stesso layout di un oggetto secondario dell'oggetto completo rispetto a se fosse un oggetto separato, quindi ci saranno vptr aggiuntivi che puntano a regioni diverse nella vtable dell'oggetto completo.
Infine, nel caso dell'ereditarietà virtuale le cose diventano ancora più complicate e potrebbero esserci più vtables per lo stesso oggetto completo con i vptr che vengono aggiornati man mano che la costruzione/distruzione si evolve (i vptr sono sempre aggiornati mentre la costruzione/distruzione si evolve, ma senza ereditarietà virtuale vptr indicherà ad VTables della base, mentre nel caso di eredità virtuale ci saranno più VTables per lo stesso tipo)
la stampa fine
qualcosa riguardo vptr/vtable non è specificato, così questo dipenderà dal compilatore per i dettagli precisi, ma i casi semplici sono gestiti lo stesso da quasi tutti i comp moderni iler (scrivo "quasi" per ogni evenienza).
Sei stato avvisato.
disposizione Oggetto: l'eredità non virtuale
Se ereditare da classi base, e hanno un vptr, si ha naturalmente come molti ereditato vptr nella classe.
La domanda è: quando il compilatore aggiungerà un vptr a una classe che ha già un vptr ereditato?
Il compilatore cercherà di evitare di aggiungere vptr ridondante:
struct B {
virtual ~B();
};
struct D : B {
virtual void foo();
};
Qui B
ha un vptr, quindi D
non ottiene il proprio vptr, si riutilizza il vptr esistente; il vtable di B
viene esteso con una voce per foo()
. Il vtable per D
è "derivato" dal vtable per B
, pseudo-codice:
struct B_vtable {
typeinfo *info; // for typeid, dynamic_cast
void (*destructor)(B*);
};
struct D_vtable : B_vtable {
void (*foo)(D*);
};
L'ammenda di stampa, ancora una volta: questa è una semplificazione di un vero e proprio vtable, per avere l'idea.
eredità virtuale
Per ereditarietà singola non virtuale, non c'è quasi spazio per la variazione tra le implementazioni. Per l'ereditarietà virtuale, ci sono molte più variazioni tra i compilatori.
struct B2 : virtual A {
};
C'è una conversione da B2*
a A*
, quindi un oggetto B2
deve fornire questa funzionalità:
- sia con un
A*
membro - sia con un elemento int:
offset_of_A_from_B2
- sia utilizzando il suo vptr, memorizzando
offset_of_A_from_B2
nel vtable
In generale, una classe sarà non riutilizzare il vptr della sua classe base virtuale (ma può in un caso molto speciale).
"' struct d: b {barra virtuale();}; // extra vtable, necessario b.vptr e d.vptr' "Non penso che ci sia un compilatore che introduce più di un vptr in una classe con SI non virtuale. – curiousguy