L'ereditarietà multipla consente a una classe di ereditare l'interfaccia e l'implementazione di più classi base. Questa è una caratteristica potente di C++, ampiamente utilizzata fin dai primi giorni del linguaggio per implementare architetture complesse.
Storia della questione: L'ereditarietà multipla è stata aggiunta in C++ come mezzo per creare componenti riutilizzabili e combinare diversi ruoli in una classe (ad esempio, quando un oggetto è sia un thread che una coda).
Problema: Le principali complessità dell'ereditarietà multipla includono il 'diamond problem' (problema dell'ereditarietà romboidale), l'ambiguità nell'accesso ai membri delle classi base e l'inconsapevolezza dell'ordine di chiamata dei costruttori/distruttori.
Soluzione: Per evitare i problemi sopra menzionati, C++ prevede l'ereditarietà virtuale. Questa garantisce che un antenato comune venga creato esattamente una volta, anche se ci sono più catene che vi conducono nell'gerarchia, e assicura l'ordine corretto di inizializzazione/distruzione.
Esempio di codice:
class A { public: int value; A() : value(1) {} }; class B : virtual public A {}; class C : virtual public A {}; class D : public B, public C {}; int main() { D d; d.value = 10; // OK, solo un A return 0; }
Caratteristiche chiave:
Se una classe base è ereditata due volte (a sinistra e a destra), quante copie di questa classe saranno in memoria dell'oggetto?
Di default due, se non viene utilizzata l'ereditarietà virtuale. Solo con l'ereditarietà virtuale ci sarà esattamente una copia.
È possibile specificare esplicitamente a quale classe base si riferisce l'accesso a un membro se si verifica un'ambiguità?
Sì, utilizzando i qualificatori:
d.B::value = 5; d.C::value = 6;
Come viene determinato l'ordine di chiamata dei costruttori e dei distruttori in caso di ereditarietà multipla?
L'ordine di chiamata dei costruttori corrisponde all'ordine di dichiarazione delle classi base nella lista di ereditarietà (da sinistra a destra), seguito dalla classe derivata stessa. Per i distruttori, il contrario.
Un programmatore implementa un sistema di logging e code tramite ereditarietà multipla, senza sospettare del diamond problem. Di conseguenza, il logger comune viene inizializzato due volte, causando conflitti al momento della liberazione delle risorse.
Pro:
Contro:
Si utilizza l'ereditarietà virtuale per il logger comune e i membri della classe vengono esplicitamente inizializzati nel costruttore.
Pro:
Contro: