L'ereditarietà multipla in C++ è potente, ma è uno strumento complicato, che spesso porta a problemi se non viene utilizzato con attenzione, problemi come il problema del diamante.
In questo articolo discuteremo del problema del diamante, di come deriva dall'ereditarietà multipla e di cosa puoi fare per risolvere il problema.
Ereditarietà multipla in C++
L'ereditarietà multipla è un caratteristica della programmazione orientata agli oggetti (OOP) dove una sottoclasse può ereditare da più di una superclasse. In altre parole, una classe figlio può avere più di un genitore.
La figura seguente mostra una rappresentazione grafica di eredità multiple.
Nel diagramma sopra, classe C ha classe A e classe B come i suoi genitori.
Se consideriamo uno scenario di vita reale, un bambino eredita da suo padre e sua madre. Quindi un figlio può essere rappresentato come una classe derivata con "Padre" e "Madre" come genitori. Allo stesso modo, possiamo avere molti di questi esempi reali di eredità multipla.
Nell'ereditarietà multipla, i costruttori di una classe ereditata vengono eseguiti nell'ordine in cui sono stati ereditati. D'altra parte, i distruttori vengono eseguiti nell'ordine inverso rispetto alla loro eredità.
Ora illustriamo l'ereditarietà multipla e verifichiamo l'ordine di costruzione e distruzione degli oggetti.
Illustrazione del codice di eredità multipla
Per l'illustrazione dell'ereditarietà multipla, abbiamo programmato esattamente la rappresentazione sopra in C++. Di seguito è riportato il codice del programma.
#includere
usando lo spazio dei nomi std;
class A //classe base A con costruttore e distruttore
{
pubblico:
A() { cout << "classe A:: Costruttore" << endl; }
~A() { cout << "classe A:: Distruttore" << endl; }
};
class B //classe base B con costruttore e distruttore
{
pubblico:
B() { cout << "classe B:: Costruttore" << endl; }
~B() { cout << "classe B:: Distruttore" << endl; }
};
class C: public B, public A //la classe derivata C eredita la classe A e poi la classe B (notare l'ordine)
{
pubblico:
C() { cout << "classe C:: Costruttore" << endl; }
~C() { cout << "classe C:: Distruttore" << endl; }
};
int main(){
Cc;
restituisce 0;
}
L'output che otteniamo dal programma precedente è il seguente:
classe B:: Costruttore
classe A:: Costruttore
classe C:: Costruttore
classe C:: Distruttore
classe A:: Distruttore
classe B:: Distruttore
Ora, se controlliamo l'output, vediamo che i costruttori vengono chiamati nell'ordine B, A e C mentre i distruttori sono nell'ordine inverso. Ora che conosciamo le basi dell'ereditarietà multipla, passiamo a discutere il problema del diamante.
Il problema del diamante, spiegato
Il problema del diamante si verifica quando una classe figlia eredita da due classi genitori che condividono entrambe una classe comune dei nonni. Ciò è illustrato nello schema seguente:
Ecco, abbiamo una classe Bambino ereditare dalle classi Padre e Madre. Queste due classi, a loro volta, ereditano la classe Persona perché sia il Padre che la Madre sono Persona.
Come mostrato nella figura, la classe Bambino eredita i tratti della classe Persona due volte, una volta dal Padre e di nuovo dalla Madre. Ciò genera ambiguità poiché il compilatore non riesce a capire in che direzione andare.
Questo scenario dà origine a un grafico dell'ereditarietà a forma di diamante ed è notoriamente chiamato "Il problema del diamante".
Illustrazione del codice del problema del diamante
Di seguito abbiamo rappresentato l'esempio precedente di ereditarietà a forma di diamante a livello di codice. Il codice è riportato di seguito:
#includere
usando lo spazio dei nomi std;
classe Persona { //classe Persona
pubblico:
Persona (int x) { cout << "Persona:: Persona (int) chiamata" << endl; }
};
class Father: public Person { //class Father eredita Persona
pubblico:
Padre (int x): Persona (x) {
cout << "Padre:: Padre (int) chiamato" << endl;
}
};
class Madre: public Person { //class Mother eredita Person
pubblico:
Madre (int x): Persona (x) {
cout << "Madre:: Madre (int) ha chiamato" << endl;
}
};
class Child: public Father, public Mother { //Il bambino eredita padre e madre
pubblico:
Figlio (int x): Madre (x), Padre (x) {
cout << "Child:: Child (int) chiamato" << endl;
}
};
int main() {
Bambino bambino (30);
}
Di seguito è riportato l'output di questo programma:
Persona:: Persona (int) chiamata
Padre:: Padre (int) chiamato
Persona:: Persona (int) chiamata
Madre:: Madre (int) ha chiamato
Bambino:: Bambino (int) chiamato
Ora puoi vedere l'ambiguità qui. Il costruttore della classe Person viene chiamato due volte: una volta quando viene creato l'oggetto della classe Father e la successiva quando viene creato l'oggetto della classe Mother. Le proprietà della classe Person vengono ereditate due volte, dando luogo ad ambiguità.
Poiché il costruttore della classe Person viene chiamato due volte, anche il distruttore verrà chiamato due volte quando l'oggetto della classe Child viene distrutto.
Ora, se hai compreso correttamente il problema, discutiamo della soluzione al problema del diamante.
Come risolvere il problema del diamante in C++
La soluzione al problema del diamante è usare il virtuale parola chiave. Trasformiamo le due classi genitore (che ereditano dalla stessa classe dei nonni) in classi virtuali per evitare due copie della classe dei nonni nella classe figlio.
Cambiamo l'illustrazione sopra e controlliamo l'output:
Illustrazione del codice per risolvere il problema del diamante
#includere
usando lo spazio dei nomi std;
classe Persona { //classe Persona
pubblico:
Persona() { cout << "Persona:: Persona() chiamata" << endl; } //Costruttore di base
Persona (int x) { cout << "Persona:: Persona (int) chiamata" << endl; }
};
class Padre: pubblico virtuale Persona { //class Padre eredita Persona
pubblico:
Padre (int x): Persona (x) {
cout << "Padre:: Padre (int) chiamato" << endl;
}
};
class Madre: pubblico virtuale Persona { //class Madre eredita Persona
pubblico:
Madre (int x): Persona (x) {
cout << "Madre:: Madre (int) ha chiamato" << endl;
}
};
class Child: public Father, public Mother { //class Child eredita padre e madre
pubblico:
Figlio (int x): Madre (x), Padre (x) {
cout << "Child:: Child (int) chiamato" << endl;
}
};
int main() {
Bambino bambino (30);
}
Qui abbiamo usato il virtuale parola chiave quando le classi Padre e Madre ereditano la classe Persona. Questo è solitamente chiamato "ereditarietà virtuale", che garantisce che venga trasmessa solo una singola istanza della classe ereditata (in questo caso, la classe Person).
In altre parole, la classe Child avrà una singola istanza della classe Person, condivisa da entrambe le classi Father e Mother. Avendo una singola istanza della classe Person, l'ambiguità viene risolta.
L'output del codice precedente è riportato di seguito:
Persona:: Persona() chiamata
Padre:: Padre (int) chiamato
Madre:: Madre (int) ha chiamato
Bambino:: Bambino (int) chiamato
Qui puoi vedere che il costruttore della classe Person viene chiamato solo una volta.
Una cosa da notare sull'ereditarietà virtuale è che anche se il costruttore parametrizzato del La classe Person viene chiamata esplicitamente dai costruttori di classi Father e Mother attraverso l'inizializzazione elenchi, verrà chiamato solo il costruttore di base della classe Person.
Questo perché esiste solo una singola istanza di una classe base virtuale condivisa da più classi che ne ereditano.
Per evitare che il costruttore di base venga eseguito più volte, il costruttore di una classe base virtuale non viene chiamato dalla classe che ne eredita. Il costruttore viene invece chiamato dal costruttore della classe concreta.
Nell'esempio sopra, la classe Child chiama direttamente il costruttore di base per la classe Person.
Imparentato: Una guida per principianti alla libreria di modelli standard in C++
Cosa succede se è necessario eseguire il costruttore parametrizzato della classe base? Puoi farlo chiamandolo esplicitamente nella classe Child piuttosto che nelle classi Father o Mother.
Il problema del diamante in C++, risolto
Il problema del diamante è un'ambiguità che sorge nell'ereditarietà multipla quando due classi genitori ereditano dalla stessa classe dei nonni ed entrambe le classi genitori sono ereditate da una singola classe figlio. Senza utilizzare l'ereditarietà virtuale, la classe figlio erediterà le proprietà della classe nonna due volte, portando all'ambiguità.
Questo può verificarsi frequentemente nel codice del mondo reale, quindi è importante affrontare tale ambiguità ogni volta che viene individuata.
Il problema del diamante viene risolto utilizzando l'ereditarietà virtuale, in cui il virtuale La parola chiave viene utilizzata quando le classi padre ereditano da una classe nonna condivisa. In questo modo, viene creata solo una copia della classe nonno e la costruzione dell'oggetto della classe nonno viene eseguita dalla classe figlio.
Vuoi imparare a programmare ma non sai da dove cominciare? Questi progetti e tutorial di programmazione per principianti ti faranno iniziare.
Leggi Avanti
- Programmazione
- C Programmazione
Iscriviti alla nostra Newsletter
Iscriviti alla nostra newsletter per consigli tecnici, recensioni, ebook gratuiti e offerte esclusive!
Clicca qui per iscriverti