C'è un equivoco: l'ereditarietà - al di fuori del concetto di puro OOP, che C++ non è - non è altro che una "composizione con un membro senza nome, con una capacità di decadimento".
L'assenza di funzioni virtuali (e il distruttore non è speciale, in questo senso) rende il tuo oggetto non polimorfico, ma se quello che stai facendo è solo "riusare il comportamento ed esporre l'interfaccia nativa" l'ereditarietà fa esattamente ciò che chiesto.
I distruttori non devono essere chiamati esplicitamente l'uno dall'altro, poiché la loro chiamata è sempre concatenata dalla specifica.
#include <iostream>
unsing namespace std;
class A
{
public:
A() { cout << "A::A()" << endl; }
~A() { cout << "A::~A()" << endl; }
void hello() { cout << "A::hello()" << endl; }
};
class B: public A
{
public:
B() { cout << "B::B()" << endl; }
~B() { cout << "B::~B()" << endl; }
void hello() { cout << "B::hello()" << endl; }
};
int main()
{
B b;
b.hello();
return 0;
}
stamperà
A::A()
B::B()
B::hello()
B::~B()
A::~A()
rendendo un incorporato in B con
class B
{
public:
A a;
B() { cout << "B::B()" << endl; }
~B() { cout << "B::~B()" << endl; }
void hello() { cout << "B::hello()" << endl; }
};
che uscita sarà esattamente lo stesso.
Il "Non derivare se il distruttore non è virtuale" non è una conseguenza obbligatoria del C++, ma solo una regola comunemente accettata non scritta (non c'è nulla nella specifica a riguardo: a parte una regola di cancellazione chiamata su base UB) che sorge prima del C++ 99, quando l'OOP per ereditarietà dinamica e funzioni virtuali era l'unico paradigma di programmazione supportato da C++.
Naturalmente, molti programmatori di tutto il mondo fatto le loro ossa con quel tipo di scuola (lo stesso che insegnano iostreams come primitivi, poi si trasferisce a matrice e puntatori, e l'ultima lezione l'insegnante dice "oh. ..cheh è anche l'STL che ha vettoriale, stringhe e altre funzionalità avanzate ") e oggi, anche se il C++ è diventato multiparadigm, insiste ancora con questa regola OOP pura.
Nel mio esempio A :: ~ A() non è virtuale esattamente come A :: ciao. Cosa significa?
Semplice: per lo stesso motivo di intervento A::hello
non si tradurrà in chiamare B::hello
, chiamando A::~A()
(da delete) non comporterà B::~B()
. Se è possibile accettare -in stile di programmazione- la prima affermazione, non vi è alcun motivo per cui non è possibile accettare il secondo. Nel mio esempio non c'è lo A* p = new B
che riceverà delete p
poiché A :: ~ A non è virtuale e So cosa significa.
Esattamente la stessa ragione che non farà, utilizzando il secondo esempio per B, A* p = &((new B)->a);
con delete p;
, anche se questo secondo caso, perfettamente duale con il primo, sembra chiunque non interessante per ragioni apparenti.
L'unico problema è "manutenzione", nel senso che - se il codice yopur è visualizzato da un programmatore OOP - lo rifiuterà, non perché è sbagliato di per sé, ma perché gli è stato detto di farlo.
Infatti, il "non derivare se il distruttore non è virtuale" è perché la maggior parte dei programmatori crede che ci siano troppi programmatori che non sanno di non poter chiamare eliminare su un puntatore a una base. (Scusate se questo non è educato, ma dopo 30 + anni di esperienza di programmazione non riesco a vedere qualsiasi altra ragione!)
Ma la tua domanda è diversa:
Calling B :: ~ B() (dalla cancellazione o per fine finale) risulterà sempre in A :: ~ A() poiché A (sia esso incorporato o ereditato) è in ogni caso parte di B.
A seguito delle osservazioni Luchian: il comportamento non definito accennato sopra una nei suoi commenti è legato a una cancellazione su un puntatore-a-an-object's-base senza distruttore virtuale.
Secondo la scuola OOP, ciò comporta la regola "non derivare se non esiste un distruttore virtuale".
Quello che sto sottolineando, qui, è che le ragioni di questa scuola dipendono dal fatto che ogni oggetto orientato OOP deve essere polimorfico e tutto ciò che è polimorfico deve essere indirizzabile da un puntatore a una base, per consentire la sostituzione dell'oggetto . Facendo questa affermazione, quella scuola sta deliberatamente cercando di rendere nullo l'intersezione tra derivata e non sostituibile, in modo che un puro programma OOP non verifichi quell'UB.
La mia posizione, semplicemente, ammette che C++ non è solo OOP, e non tutti gli oggetti C++ DEVONO ESSERE OOP orientati di default e, ammettere che OOP non è sempre un bisogno necessario necessariamente a servizio di sostituzione OOP.
std :: map NON è polimorfico, quindi NON è sostituibile. MyMap è lo stesso: NON polimorfo e NON sostituibile.
È sufficiente riutilizzare std :: map ed esporre la stessa interfaccia std :: map. E l'ereditarietà è solo il modo di evitare una lunga serie di funzioni riscritte che chiamano solo quelle riutilizzate.
MyMap non avrà dtor virtuale come std :: map non ne ha uno. E questo -per me- è sufficiente per dire a un programmatore C++ che questi non sono oggetti polimorfi e che non devono essere usati uno nel posto dell'altro.
Devo ammettere che questa posizione non è oggi condivisa dalla maggior parte degli esperti di C++. Ma penso (la mia unica opinione personale) questo è solo per la loro storia, che si riferisce a OOP come un dogma da servire, non a causa di un bisogno di C++. Per me il C++ non è un puro linguaggio OOP e non deve necessariamente seguire sempre il paradigma OOP, in un contesto in cui OOP non è seguito o richiesto.
+1 Preferire sempre la composizione anziché l'ereditarietà. Vorrei ancora che ci fosse un modo per ridurre tutto il codice di caldaia necessario per il confezionamento. – daramarak
@daramarak: anche io, se solo qualcosa come "using attribute.insert;" potrebbe funzionare! D'altra parte, è piuttosto raro che in realtà tu abbia bisogno di tutti i metodi, e il wrapping dà l'opportunità di dare un nome significativo e prendere tipi di livello superiore :) –
@daramarak: * Ancora vorrei che ci fosse un modo per ridurre tutto il codice di codice necessario per il wrapping *: sì, c'è: eredità. Ma i programmatori sono convinti di non doverlo usare ... perché tendono sempre a interpretarlo come "è un". Ma questo non è un requisito, solo una pubblica convinzione. –