Come posso evitare il diamante della morte quando si utilizza l’ereditarietà multipla?

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

So cosa significa, ma quali misure posso adottare per evitarlo?

Un esempio pratico:

class A {}; class B : public A {}; class C : public A {}; class D : public B, public C {}; 

Si noti come la class D erediti da entrambi i B & C. Ma entrambi i B & C ereditano da A. Ciò comporterà due copie della class A incluse nel vtable.

Per risolvere questo, abbiamo bisogno di ereditarietà virtuale. È una class A che deve essere virtualmente ereditata. Quindi, questo risolverà il problema:

 class A {}; class B : virtual public A {}; class C : virtual public A {}; class D : public B, public C {}; 

eredità virtuale. Ecco a cosa serve.

Mi limiterei a utilizzare l’ereditarietà multipla solo delle interfacce. Sebbene l’eredità multipla delle classi sia attraente a volte, può anche essere confusa e dolorosa se ci si basa su di essa regolarmente.

L’ereditarietà è un’arma forte e forte. Usalo solo quando ne hai veramente bisogno. In passato, l’eredità del diamante era un segnale che stavo andando lontano con la classificazione, dicendo che un utente è un “impiegato” ma è anche un “ascoltatore di widget”, ma anche un …

In questi casi, è facile colpire più problemi di ereditarietà.

Li ho risolti usando la composizione e i riferimenti al proprietario:

Prima:

 class Employee : public WidgetListener, public LectureAttendee { public: Employee(int x, int y) WidgetListener(x), LectureAttendee(y) {} }; 

Dopo:

 class Employee { public: Employee(int x, int y) : listener(this, x), attendee(this, y) {} WidgetListener listener; LectureAttendee attendee; }; 

Sì, i diritti di accesso sono diversi, ma se riesci a cavarcanvas con un tale approccio, senza duplicare il codice, è meglio perché è meno potente. (Puoi risparmiare energia quando non hai alternative.)

 class A {}; class B : public A {}; class C : public A {}; class D : public B, public C {}; 

In questo gli attributi di Classe A sono ripetuti due volte in Classe D, il che rende più l’utilizzo della memoria … Quindi per risparmiare memoria creiamo un attributo virtuale per tutti gli attributi ereditati di class A che sono memorizzati in un Vtable.

Bene, la cosa bella del Dreaded Diamond è che si tratta di un errore quando si verifica. Il modo migliore per evitare è capire in anticipo la struttura di ereditarietà. Ad esempio, un progetto su cui lavoro ha Viewers ed Editors. Gli editor sono sottoclassi logiche di Viewer, ma poiché tutti i Viewer sono sottoclassi – TextViewer, ImageViewer, ecc., L’Editor non deriva dal Viewer, consentendo così alle classi finali di TextEditor, ImageEditor di evitare il diamante.

Nei casi in cui il diamante non è evitabile, utilizzando l’ereditarietà virtuale. L’avvertimento più grande, tuttavia, con basi virtuali, è che il costruttore per la base virtuale deve essere chiamato dalla class più derivata, il che significa che una class che deriva virtualmente non ha alcun controllo sui parametri del costruttore. Inoltre, la presenza di una base virtuale tende ad incorrere in una penalità di prestazioni / spazio durante il lancio attraverso la catena, anche se non credo che ci sia molta penalità per oltre il primo.

Inoltre, puoi sempre usare il diamante se sei esplicito su quale base desideri utilizzare. A volte è l’unico modo.

Suggerirei un design di class migliore. Sono sicuro che ci sono alcuni problemi che vengono risolti meglio attraverso l’ereditarietà multipla, ma controlla se c’è un altro modo per prima.

In caso contrario, utilizzare le funzioni / interfacce virtuali.

Utilizzare l’ereditarietà per delega. Quindi entrambe le classi puntano a una base A, ma devono implementare metodi che reindirizzano a A. Ha l’effetto collaterale di trasformare membri protetti di A in membri “privati” in B, C e D, ma ora non lo fai bisogno di virtuale, e tu non hai un diamante.