Come viene implementata l'ereditarietà multipla di C ++?
Domanda
La singola eredità è facile da implementare. Ad esempio, in C, l'ereditarietà può essere simulata come:
struct Base { int a; }
struct Descendant { Base parent; int b; }
Ma con ereditarietà multipla, il compilatore deve disporre più genitori all'interno della classe di nuova costruzione. Come viene fatto?
Il problema che vedo sorgere è: i genitori dovrebbero essere sistemati 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. Cose virtuali simili sono richieste con i virtuali.
Soluzione
Il seguente documento del creatore di C ++ descrive una possibile implementazione di eredità multipla:
Ereditarietà multipla per C ++ - Bjarne Stroustrup
Altri suggerimenti
C'era questo piuttosto vecchio articolo MSDN su come è stato implementato in VC ++ .
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. Cose difficili simili con i virtuali.
Con l'eredità non virutale questo è meno complicato di quanto si possa pensare: nel momento in cui viene compilato il cast, il compilatore conosce l'esatto layout della classe derivata (dopo tutto, il compilatore ha fatto il layout). Di solito tutto ciò che accade è un offset fisso (che può essere zero per una delle classi base) viene aggiunto / sottratto dal puntatore della classe derivata.
Con l'eredità virutale è forse un po 'più complesso - può implicare afferrare 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 effettivamente funziona)
I genitori sono disposti nell'ordine in cui sono stati specificati:
class Derived : A, B {} // A comes first, then B
class Derived : B, A {} // B comes first, then A
Il tuo secondo caso viene gestito in un modo specifico del compilatore. Un metodo comune consiste nell'utilizzare puntatori più grandi della dimensione del puntatore della piattaforma, per archiviare dati extra.
Questo è un problema interessante che in realtà non è specifico per C ++. Le cose diventano più complesse anche quando hai una lingua con invio multiplo e eredità multipla (ad es. CLOS).
Le persone hanno già notato che esistono diversi modi per affrontare il problema. Potresti trovare interessante leggere un po 'i Protocolli Meta-Object (MOP) in questo contesto ...
Dipende interamente dal compilatore, ma credo che sia generalmente fatto attraverso una struttura gerarchica di vtables.
Ho eseguito un 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);
}
L'output è:
Descendant: 0xbfc0e3e0, casted BaseB: 0xbfc0e3e4, casted back Descendant: 0xbfc0e3e0
È bene rendersi conto che il casting statico non significa sempre "cambiare il tipo senza toccare il contenuto". (Bene, quando i tipi di dati non si adattano l'un l'altro, allora ci sarà anche un'interferenza nel contenuto, ma è una situazione diversa IMO).