19

L'ereditarietà singola è facile da implementare. Ad esempio, in C, l'eredità può essere simulato come:Come viene implementata l'ereditarietà multipla di C++?

struct Base { int a; } 
struct Descendant { Base parent; int b; } 

Ma con l'ereditarietà multipla, il compilatore deve disporre più i genitori all'interno della classe di nuova costruzione. Com'è fatto?

Il problema che vedo sorgere è: i genitori dovrebbero essere organizzati in AB o BA, o forse anche in altro modo? E poi, se faccio un cast:

SecondBase * base = (SecondBase *) &object_with_base1_and_base2_parents; 

Il compilatore deve considerare se modificare o meno il puntatore originale. È necessario qualcosa di complicato con i virtual.

+0

http://en.wikipedia.org/wiki/Diamond_problem – Dario

+0

La simulazione C dimentica il puntatore VTable (dettaglio implementazione). –

+1

@Dario: questo articolo tratta dei problemi di sovraccarico dell'ereditarietà multipla ma non contiene nulla sul layout degli oggetti e sulla fusione degli oggetti in C++. – mmmmmmmm

risposta

10

Il seguente documento dal creatore di C++ descrive una possibile implementazione dell'ereditarietà multipla:

Multiple Inheritance for C++ - Bjarne Stroustrup

+3

Link alternativo: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.23.4735&rep=rep1&type=pdf –

+0

Dopo leggendo entrambi, a seconda di cosa stai cercando. Questo (quello di _Stroustrup_) è abbastanza dettagliato e copre la teoria. Quello proposto da _Nemanja_ sotto (quello di _MSDN_) è più semplice e copre un'implementazione reale. –

+0

Entrambi i link sono morti, ma https://www.usenix.org/publications/compsystems/1989/fall_stroustrup.pdf funziona per me. –

5

C'era this pretty old MSDN article su come è stato implementato in VC++.

+1

Sebbene questo collegamento possa rispondere alla domanda, è meglio includere qui le parti essenziali della risposta e fornire il link per riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia. - [Dalla recensione] (/ recensione/post di bassa qualità/18125623) – CDspace

0

Dipende interamente dal compilatore come è fatto, ma credo che la sua struttura gerarchica di vtables sia generalmente eseguita.

1

I genitori sono disposti nell'ordine in cui stanno specificati:

class Derived : A, B {} // A comes first, then B 

class Derived : B, A {} // B comes first, then A 

Il secondo caso viene gestito in modo specifico del compilatore. Un metodo comune utilizza puntatori più grandi della dimensione del puntatore della piattaforma, per memorizzare dati aggiuntivi.

+0

Questo è probabilmente comune, ma non credo sia necessario. –

+0

L'ho visto dall'altra parte. Tutto dipende dall'implementazione. –

+0

L'ordine ha solo un effetto sull'ordine delle chiamate del costruttore. Ma il layout non è specificato. Ho fatto dei test qualche tempo fa, e GCC mette le basi vuote prima nella memoria, per utilizzare l'ottimizzazione della classe base vuota. –

1

Questo è un problema interessante che in realtà non è specifico per C++. Le cose diventano più complesse anche quando si ha una lingua con invio multiplo e ereditarietà multipla (ad es. CLOS).

Le persone hanno già notato che ci sono diversi modi per affrontare il problema. Si potrebbe trovare un po 'la lettura sui protocolli di Meta-Object (POM) interessante in questo contesto ...

+1

Penso che sia molto più facile da implementare se la lingua non supporta i puntatori "grezzi" agli oggetti refernce e nessuna compatibilità con le versioni precedenti del POD. Perché se fanno il lancio di puntatori agli oggetti è molto difficile. Una lingua potrebbe aggiungere il maggior numero di meta-informazioni alla classe di riferimento e all'istanza di classe che desidera. In C++ questo non potrebbe essere fatto molto facilmente. – mmmmmmmm

5

E poi, se devo fare un cast:

SecondBase base = (SecondBase *) object_with_base1_and_base2_parents; 

il compilatore deve considerare la possibilità di alterare o non il puntatore originale. Cose difficili simili con i virtuali.

Con l'ereditarietà non virutal questo è meno difficile di quanto si possa pensare - al punto in cui è stato compilato il cast, il compilatore conosce l'esatta disposizione della classe derivata (dopo tutto, il compilatore ha fatto il layout). Solitamente tutto ciò che accade è un offset fisso (che può essere zero per una delle classi base) aggiunto/sottratto dal puntatore della classe derivata.

Con l'ereditarietà virutale è forse un po 'più complesso - potrebbe coinvolgere l'acquisizione di un offset da un vtbl (o simile).

Il libro di Stan Lippman, "Inside the C++ Object Model" ha ottime descrizioni di come questa roba potrebbe (e spesso funziona davvero).

0

ho eseguito semplice esperimento:

class BaseA { int a; }; 
class BaseB { int b; }; 
class Descendant : public BaseA, BaseB {}; 
int main() { 
     Descendant d; 
     BaseB * b = (BaseB*) &d; 
     Descendant *d2 = (Descendant *) b; 
     printf("Descendant: %p, casted BaseB: %p, casted back Descendant: %p\n", &d, b, d2); 
} 

uscita è:

Descendant: 0xbfc0e3e0, casted BaseB: 0xbfc0e3e4, casted back Descendant: 0xbfc0e3e0 

E 'bello rendersi conto che di colata statica non sempre significa "cambiare il tipo senza toccare il contenuto". (Bene, quando i tipi di dati non si adattano a vicenda, allora ci sarà anche un'interferenza nei contenuti, ma è diversa la situazione IMO).