Quando utilizzare i distruttori virtuali?


1487

Ho una solida conoscenza della maggior parte della teoria OO, ma l'unica cosa che mi confonde molto sono i distruttori virtuali.

Ho pensato che il distruttore venisse sempre chiamato, non importa cosa e per ogni oggetto nella catena.

Quando hai intenzione di renderli virtuali e perché?


6
Vedi questo: Virtual Destructor
Naveen,

146
Ogni distruttore a terra viene chiamato, non importa quale. virtualsi assicura che inizi nella parte superiore anziché nel mezzo.
Mooing Duck,


@MooingDuck è un commento un po 'fuorviante.
Euri Pinhollow,

1
@FranklinYu è bello che tu l'abbia chiesto perché ora non riesco a vedere alcun problema con quel commento (tranne cercare di dare una risposta nei commenti).
Euri Pinhollow,

Risposte:


1573

I distruttori virtuali sono utili quando si potrebbe potenzialmente eliminare un'istanza di una classe derivata attraverso un puntatore alla classe base:

class Base 
{
    // some virtual methods
};

class Derived : public Base
{
    ~Derived()
    {
        // Do some important cleanup
    }
};

Qui noterai che non ho dichiarato il distruttore di Base virtual. Ora diamo un'occhiata al seguente frammento:

Base *b = new Derived();
// use b
delete b; // Here's the problem!

Poiché il distruttore di Base non è virtualed bè un Base*puntamento a un Derivedoggetto, delete bha un comportamento indefinito :

[In delete b], se il tipo statico dell'oggetto da eliminare è diverso dal suo tipo dinamico, il tipo statico deve essere una classe base del tipo dinamico dell'oggetto da eliminare e il tipo statico deve avere un distruttore virtuale o il comportamento non è definito .

Nella maggior parte delle implementazioni, la chiamata al distruttore verrà risolta come qualsiasi codice non virtuale, il che significa che verrà chiamato il distruttore della classe base ma non quello della classe derivata, con conseguente perdita di risorse.

Per riassumere, crea sempre i distruttori delle classi di base virtualquando devono essere manipolati polimorficamente.

Se si desidera impedire la cancellazione di un'istanza tramite un puntatore della classe base, è possibile rendere il distruttore della classe base protetto e non virtuale; così facendo, il compilatore non ti permetterà di chiamare deleteun puntatore della classe base.

Puoi saperne di più sulla virtualità e sul distruttore di classe di base virtuale in questo articolo da Herb Sutter .


174
Questo spiegherebbe perché ho avuto enormi perdite usando una fabbrica che avevo fatto prima. Tutto ha un senso adesso. Grazie
Lodle,

8
Bene, questo è un cattivo esempio in quanto non ci sono membri di dati. Cosa succede se Basee Derivedhanno tutte le variabili di archiviazione automatiche? cioè non esiste un codice personalizzato "speciale" o aggiuntivo da eseguire nel distruttore. Va bene quindi smettere di scrivere qualsiasi distruttore? O la classe derivata avrà ancora una perdita di memoria?
Bobobobo,


28
Dall'articolo di Herb Sutter: "Linea guida n. 4: un distruttore di classe base dovrebbe essere pubblico e virtuale, oppure protetto e non virtuale".
Sundae,

3
Sempre dall'articolo - "se elimini polimorficamente senza un distruttore virtuale, evochi il temuto spettro di" comportamento indefinito ", uno spettro che personalmente preferirei non incontrare in un vicolo moderatamente ben illuminato, grazie mille." lol
Bondolin,

219

Un costruttore virtuale non è possibile ma è possibile un distruttore virtuale. Sperimentiamo .......

#include <iostream>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

Il codice sopra riportato genera quanto segue:

Base Constructor Called
Derived constructor called
Base Destructor called

La costruzione dell'oggetto derivato segue la regola di costruzione ma quando eliminiamo il puntatore "b" (puntatore di base) abbiamo scoperto che viene chiamato solo il distruttore di base. Ma questo non deve succedere. Per fare la cosa appropriata, dobbiamo rendere virtuale il distruttore di base. Ora vediamo cosa succede nel seguente modo:

#include <iostream>

using namespace std;

class Base
{ 
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    virtual ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

L'output è cambiato come segue:

Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called

Quindi la distruzione del puntatore di base (che prende un'allocazione sull'oggetto derivato!) Segue la regola di distruzione, cioè prima la Derivata, poi la Base. D'altra parte, non c'è niente come un costruttore virtuale.


1
"costruttore virtuale non è possibile" significa che non è necessario scrivere costruttore virtuale da soli. La costruzione di oggetti derivati ​​deve seguire la catena di costruzione da derivata a base. Quindi non è necessario scrivere la parola chiave virtuale per il costruttore. Grazie
Tunvir Rahman Tusher,

4
@Murkantilism, "i costruttori virtuali non possono essere fatti" è vero. Un costruttore non può essere contrassegnato come virtuale.
cmeub

1
@cmeub, Ma c'è un linguaggio per ottenere ciò che vorresti da un costruttore virtuale. Vedi parashift.com/c++-faq-lite/virtual-ctors.html
cape1232

@TunvirRahmanTusher potresti per favore spiegare perché il Base Destructor si chiama ??
rimalonfire l'

. @rimiro sua automatica da C ++ è possibile seguire il link stackoverflow.com/questions/677620/...
Tunvir Rahman Tusher

195

Dichiarare i distruttori virtuali in classi di base polimorfiche. Questo è l'articolo 7 dell'Efficace C ++ di Scott Meyers . Meyers prosegue riassumendo che se una classe ha una funzione virtuale, dovrebbe avere un distruttore virtuale e che le classi non progettate per essere classi base o non progettate per essere utilizzate polimorficamente non dovrebbero dichiarare distruttori virtuali.


14
+ "Se una classe ha una funzione virtuale, dovrebbe avere un distruttore virtuale e che le classi non progettate per essere classi base o non progettate per essere utilizzate polimorficamente non dovrebbero dichiarare distruttori virtuali.": Ci sono casi in cui ha senso infrangere questa regola? In caso contrario, avrebbe senso che il compilatore controllasse questa condizione e generasse un errore se non è soddisfatto?
Giorgio,

@Giorgio Non conosco eccezioni alla regola. Ma non mi classificherei come esperto di C ++, quindi potresti voler pubblicare questo come una domanda separata. Un avviso del compilatore (o un avviso da uno strumento di analisi statica) ha senso per me.
Bill the Lizard,

10
Le classi possono essere progettate per non essere eliminate tramite il puntatore di un certo tipo, ma hanno ancora funzioni virtuali - un esempio tipico è un'interfaccia di callback. Uno non elimina la sua implementazione tramite un puntatore all'interfaccia di callback poiché è solo per la sottoscrizione, ma ha funzioni virtuali.
dascandy

3
@dascandy Esatto - quella o tutte le molte altre situazioni in cui utilizziamo il comportamento polimorfico ma non eseguiamo la gestione della memoria tramite puntatori - ad esempio mantenendo oggetti di durata automatica o statica, con i puntatori usati solo come percorsi di osservazione. Nessuna necessità / scopo nell'implementazione di un distruttore virtuale in tali casi. Dato che stiamo solo citando le persone qui, preferisco Sutter dall'alto: "Linea guida n. 4: un distruttore di classe base dovrebbe essere pubblico e virtuale, oppure protetto e non virtuale". Quest'ultimo assicura che chiunque tenti di eliminare accidentalmente tramite un puntatore di base venga mostrato l'errore dei propri modi
underscore_d

1
@Giorgio In realtà esiste un trucco che si può usare ed evitare una chiamata virtuale a un distruttore: associare tramite un riferimento const un oggetto derivato a una base, come const Base& = make_Derived();. In questo caso, Derivedverrà chiamato il distruttore del valore, anche se non è virtuale, quindi si salva il sovraccarico introdotto da vtables / vpointers. Naturalmente l'ambito è piuttosto limitato. Andrei Alexandrescu lo ha menzionato nel suo libro Modern C ++ Design .
vsoftco,

46

Inoltre, tenere presente che l'eliminazione di un puntatore della classe base quando non è presente un distruttore virtuale comporterà un comportamento indefinito . Qualcosa che ho imparato solo di recente:

Come dovrebbe comportarsi l'override di cancellazione in C ++?

Uso C ++ da anni e riesco ancora ad impiccarmi.


Ho dato un'occhiata a quella tua domanda e ho visto che avevi dichiarato il distruttore di base virtuale. Quindi "l'eliminazione di un puntatore della classe base quando non c'è un distruttore virtuale comporterà un comportamento indefinito" rimane valido rispetto a quella tua domanda? Poiché, in quella domanda, quando si chiamava delete, la classe derivata (creata dal suo nuovo operatore) viene prima controllata per una versione compatibile. Dal momento che ne ha trovato uno lì, è stato chiamato. Quindi, non pensi che sarebbe meglio dire come "l'eliminazione di un puntatore della classe base quando non c'è un distruttore comporterà un comportamento indefinito"?
ubuntugod,

È praticamente la stessa cosa. Il costruttore predefinito non è virtuale.
BigSandwich,

41

Rendi virtuale il distruttore ogni volta che la tua classe è polimorfica.


13

Chiamare il distruttore tramite un puntatore a una classe base

struct Base {
  virtual void f() {}
  virtual ~Base() {}
};

struct Derived : Base {
  void f() override {}
  ~Derived() override {}
};

Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived

La chiamata del distruttore virtuale non è diversa da qualsiasi altra chiamata di funzione virtuale.

Per base->f(), la chiamata verrà inviata a Derived::f(), ed è lo stesso per base->~Base()- la sua funzione prevalente - Derived::~Derived()verrà chiamata.

Lo stesso accade quando il distruttore viene chiamato indirettamente, ad es delete base;. La deletedichiarazione chiamerà a base->~Base()cui verrà inviata Derived::~Derived().

Classe astratta con distruttore non virtuale

Se non si intende eliminare l'oggetto tramite un puntatore alla sua classe base, non è necessario disporre di un distruttore virtuale. Basta farlo in protectedmodo che non venga chiamato accidentalmente:

// library.hpp

struct Base {
  virtual void f() = 0;

protected:
  ~Base() = default;
};

void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.

//-------------------
// application.cpp

struct Derived : Base {
  void f() override { ... }
};

int main() {
  Derived derived;
  CallsF(derived);
  // No need for virtual destructor here as well.
}

È necessario dichiarare esplicitamente ~Derived()in tutte le classi derivate, anche se è giusto ~Derived() = default? O è implicito nella lingua (che rende sicuro omettere)?
Ponkadoodle,

@Wallacoloo no, dichiaralo solo quando è necessario. Ad esempio per mettere in protectedsezione, o per assicurarsi che sia virtuale utilizzando override.
Abyx,

9

Mi piace pensare alle interfacce e alle implementazioni delle interfacce. In C ++, l'interfaccia speak è pura classe virtuale. Destructor fa parte dell'interfaccia e dovrebbe essere implementato. Pertanto il distruttore dovrebbe essere puro virtuale. Che ne dici di costruttore? Il costruttore in realtà non fa parte dell'interfaccia perché l'oggetto viene sempre istanziato esplicitamente.


2
È una prospettiva diversa sulla stessa domanda. Se pensiamo in termini di interfacce anziché di classe base vs classe derivata, allora è naturale conclusione: se fa parte dell'interfaccia piuttosto che renderla virtuale. Se non lo è.
Dragan Ostojic,

2
+1 per affermare la somiglianza del concetto di interfaccia OO e una classe virtuale pura C ++ . Per quanto riguarda il distruttore dovrebbe essere implementato : spesso non è necessario. A meno che una classe non stia gestendo una risorsa come la memoria allocata dinamicamente non elaborata (ad esempio, non tramite un puntatore intelligente), un handle di file o un handle di database, l'utilizzo del distruttore predefinito creato dal compilatore va bene nelle classi derivate. E nota che se un distruttore (o qualsiasi funzione) viene dichiarato virtualin una classe base, si trova automaticamente virtualin una classe derivata, anche se non è dichiarato così.
DavidRR

Questo manca il dettaglio cruciale che il distruttore non fa necessariamente parte dell'interfaccia. Si possono facilmente programmare classi con funzioni polimorfiche ma che il chiamante non gestisce / non può cancellare. Quindi un distruttore virtuale non ha scopo. Naturalmente, per garantire ciò, il distruttore non virtuale - probabilmente predefinito - dovrebbe essere non pubblico. Se dovessi indovinare, direi che tali classi sono più spesso utilizzate internamente ai progetti, ma ciò non li rende meno rilevanti come esempio / sfumatura in tutto questo.
underscore_d

8

La parola chiave virtuale per distruttore è necessaria quando si desidera che distruttori diversi seguano l'ordine corretto mentre gli oggetti vengono eliminati tramite il puntatore della classe base. per esempio:

Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ; 

Se il distruttore della classe base è virtuale, gli oggetti verranno distrutti in un ordine (prima l'oggetto derivato, quindi la base). Se il distruttore della classe base NON è virtuale, verrà eliminato solo l'oggetto della classe base (poiché il puntatore è della classe base "Base * myObj"). Quindi ci sarà perdita di memoria per l'oggetto derivato.


7

Per essere semplice, Virtual destructor consiste nel distruggere le risorse in un ordine corretto, quando si elimina un puntatore della classe base che punta all'oggetto classe derivato.

 #include<iostream>
 using namespace std;
 class B{
    public:
       B(){
          cout<<"B()\n";
       }
       virtual ~B(){ 
          cout<<"~B()\n";
       }
 };
 class D: public B{
    public:
       D(){
          cout<<"D()\n";
       }
       ~D(){
          cout<<"~D()\n";
       }
 };
 int main(){
    B *b = new D();
    delete b;
    return 0;
 }

OUTPUT:
B()
D()
~D()
~B()

==============
If you don't give ~B()  as virtual. then output would be 
B()
D()
~B()
where destruction of ~D() is not done which leads to leak


Non avere il distruttore virtuale di base e chiamare deleteun puntatore di base porta a comportamenti indefiniti.
James Adkison,

@JamesAdkison perché porta a un comportamento indefinito ??
rimalonfire l'

@rimiro È quello che dice lo standard . Non ne ho una copia ma il link ti porta a un commento in cui qualcuno fa riferimento alla posizione all'interno dello standard.
James Adkison,

@rimiro "Se la cancellazione, quindi, può essere eseguita polimorficamente attraverso l'interfaccia della classe base, allora deve comportarsi virtualmente e deve essere virtuale. In effetti, il linguaggio lo richiede - se si elimina polimorficamente senza un distruttore virtuale, si evoca lo spettro temuto di "comportamento indefinito", uno spettro che personalmente preferirei non incontrare in un vicolo moderatamente ben illuminato, grazie mille. " ( gotw.ca/publications/mill18.htm ) - Herb Sutter
James Adkison

4

I distruttori di classe di base virtuale sono la "migliore pratica": è consigliabile utilizzarli sempre per evitare perdite di memoria (difficili da rilevare). Usandoli, puoi essere sicuro che tutti i distruttori della catena di eredità delle tue classi vengano chiamati (nell'ordine corretto). Ereditare da una classe base usando il distruttore virtuale rende automaticamente anche il distruttore della classe ereditante (quindi non è necessario riscrivere "virtuale" nella dichiarazione del distruttore della classe ereditante).


4

Se usi shared_ptr(solo shared_ptr, non unique_ptr), non devi avere il distruttore di classe base virtuale:

#include <iostream>
#include <memory>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){ // not virtual
        cout << "Base Destructor called\n";
    }
};

class Derived: public Base
{
public:
    Derived(){
        cout << "Derived constructor called\n";
    }
    ~Derived(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    shared_ptr<Base> b(new Derived());
}

produzione:

Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called

Sebbene ciò sia possibile, scoraggerei chiunque dall'utilizzarlo. Il sovraccarico di un distruttore virtuale è minuscolo e questo rende solo possibile sbagliare, specialmente da un programmatore meno esperto, che non lo sa. Quella piccola virtualparola chiave potrebbe salvarti da molta agonia.
Michal Štein,

3

Cos'è un distruttore virtuale o come usare un distruttore virtuale

Un distruttore di classe è una funzione con lo stesso nome della classe che precede con ~ che riallocherà la memoria allocata dalla classe. Perché abbiamo bisogno di un distruttore virtuale

Vedi l'esempio seguente con alcune funzioni virtuali

Nell'esempio viene inoltre spiegato come convertire una lettera in maiuscolo o in minuscolo

#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
  //void convertch(){};
  virtual char* convertChar() = 0;
  ~convertch(){};
};

class MakeLower :public convertch
{
public:
  MakeLower(char *passLetter)
  {
    tolower = true;
    Letter = new char[30];
    strcpy(Letter, passLetter);
  }

  virtual ~MakeLower()
  {
    cout<< "called ~MakeLower()"<<"\n";
    delete[] Letter;
  }

  char* convertChar()
  {
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] + 32;
    return Letter;
  }

private:
  char *Letter;
  bool tolower;
};

class MakeUpper : public convertch
{
public:
  MakeUpper(char *passLetter)
  {
    Letter = new char[30];
    toupper = true;
    strcpy(Letter, passLetter);
  }

  char* convertChar()
  {   
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] - 32;
    return Letter;
  }

  virtual ~MakeUpper()
  {
    cout<< "called ~MakeUpper()"<<"\n";
    delete Letter;
  }

private:
  char *Letter;
  bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{
  convertch *makeupper = new MakeUpper("hai"); 
  cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";     
  delete makeupper;
  convertch *makelower = new MakeLower("HAI");;
  cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" "; 
  delete makelower;
  return 0;
}

Dall'esempio sopra puoi vedere che non viene chiamato il distruttore per le classi MakeUpper e MakeLower.

Guarda il prossimo esempio con il distruttore virtuale

#include "stdafx.h"
#include<iostream>

using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor

};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
      delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;

}

return Letter;
}

private:
char *Letter;
bool tolower;

};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{

size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
      cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{

convertch *makeupper = new MakeUpper("hai");

cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";

delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";


delete makelower;
return 0;
}

Il distruttore virtuale chiamerà esplicitamente il distruttore di runtime più derivato della classe in modo che sia in grado di cancellare l'oggetto in modo corretto.

O visita il link

https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138


2

quando è necessario chiamare il distruttore di classe derivato dalla classe di base. è necessario dichiarare il distruttore di classe di base virtuale in classe di base.


2

Penso che il nocciolo di questa domanda sia sui metodi virtuali e sul polimorfismo, non specificamente sul distruttore. Ecco un esempio più chiaro:

class A
{
public:
    A() {}
    virtual void foo()
    {
        cout << "This is A." << endl;
    }
};

class B : public A
{
public:
    B() {}
    void foo()
    {
        cout << "This is B." << endl;
    }
};

int main(int argc, char* argv[])
{
    A *a = new B();
    a->foo();
    if(a != NULL)
    delete a;
    return 0;
}

Stampa:

This is B.

Senza di virtualesso verrà stampato:

This is A.

E ora dovresti capire quando usare i distruttori virtuali.


No, questo ricostruisce solo le basi assolute delle funzioni virtuali, ignorando totalmente la sfumatura di quando / perché il distruttore dovrebbe essere uno - che non è così intuitivo, quindi perché l'OP ha posto la domanda. (Inoltre, perché l'allocazione dinamica non necessaria qui? Basta fare B b{}; A& a{b}; a.foo();. Controllare NULL- che dovrebbe essere nullptr- prima di deleteing - con indendimento errato - non è richiesto: delete nullptr;è definito come no-op. Se qualcosa, dovresti averlo verificato prima di chiamare ->foo(), come comportamento altrimenti indefinito può verificarsi se in newqualche modo fallito.)
underscore_d

2
È sicuro chiamare deleteun NULLpuntatore (cioè non hai bisogno della if (a != NULL)guardia).
James Adkison,

@SaileshD Sì, lo so. Questo è quello che ho detto nel mio commento
James Adkison,

1

Ho pensato che sarebbe stato utile discutere del comportamento "indefinito", o almeno del comportamento indefinito "crash" che può verificarsi quando si elimina attraverso una classe base (/ struct) senza un distruttore virtuale, o più precisamente nessuna vtable. Il codice seguente elenca alcune semplici strutture (lo stesso sarebbe vero per le classi).

#include <iostream>
using namespace std;

struct a
{
    ~a() {}

    unsigned long long i;
};

struct b : a
{
    ~b() {}

    unsigned long long j;
};

struct c : b
{
    ~c() {}

    virtual void m3() {}

    unsigned long long k;
};

struct d : c
{
    ~d() {}

    virtual void m4() {}

    unsigned long long l;
};

int main()
{
    cout << "sizeof(a): " << sizeof(a) << endl;
    cout << "sizeof(b): " << sizeof(b) << endl;
    cout << "sizeof(c): " << sizeof(c) << endl;
    cout << "sizeof(d): " << sizeof(d) << endl;

    // No issue.

    a* a1 = new a();
    cout << "a1: " << a1 << endl;
    delete a1;

    // No issue.

    b* b1 = new b();
    cout << "b1: " << b1 << endl;
    cout << "(a*) b1: " << (a*) b1 << endl;
    delete b1;

    // No issue.

    c* c1 = new c();
    cout << "c1: " << c1 << endl;
    cout << "(b*) c1: " << (b*) c1 << endl;
    cout << "(a*) c1: " << (a*) c1 << endl;
    delete c1;

    // No issue.

    d* d1 = new d();
    cout << "d1: " << d1 << endl;
    cout << "(c*) d1: " << (c*) d1 << endl;
    cout << "(b*) d1: " << (b*) d1 << endl;
    cout << "(a*) d1: " << (a*) d1 << endl;
    delete d1;

    // Doesn't crash, but may not produce the results you want.

    c1 = (c*) new d();
    delete c1;

    // Crashes due to passing an invalid address to the method which
    // frees the memory.

    d1 = new d();
    b1 = (b*) d1;
    cout << "d1: " << d1 << endl;
    cout << "b1: " << b1 << endl;
    delete b1;  

/*

    // This is similar to what's happening above in the "crash" case.

    char* buf = new char[32];
    cout << "buf: " << (void*) buf << endl;
    buf += 8;
    cout << "buf after adding 8: " << (void*) buf << endl;
    delete buf;
*/
}

Non sto suggerendo se hai bisogno di distruttori virtuali o meno, anche se penso che in generale sia una buona pratica averli. Sto solo sottolineando il motivo per cui potresti finire con un arresto anomalo se la tua classe di base (/ struct) non ha una vtable e la tua classe derivata (/ struct) lo fa e tu elimini un oggetto tramite una classe di base (/ struct) puntatore. In questo caso, l'indirizzo che passi alla routine gratuita dell'heap non è valido e quindi il motivo dell'incidente.

Se esegui il codice sopra vedrai chiaramente quando si verifica il problema. Quando il puntatore this della classe di base (/ struct) è diverso dal puntatore this della classe derivata (/ struct) si imbatterà in questo problema. Nell'esempio sopra, struct aeb non hanno vtables. le strutture ce d hanno vtables. Quindi un puntatore a o b all'istanza di oggetto ac o d verrà riparato per tenere conto della vtable. Se si passa questo puntatore a o b per eliminarlo, si arresterà in modo anomalo a causa dell'indirizzo non valido per la routine gratuita dell'heap.

Se si prevede di eliminare le istanze derivate che hanno vtables dai puntatori della classe base, è necessario assicurarsi che la classe base abbia una vtable. Un modo per farlo è quello di aggiungere un distruttore virtuale, che potresti voler comunque ripulire correttamente le risorse.


0

Una definizione di base virtualè se determina se una funzione membro di una classe può essere sovrascritta nelle sue classi derivate.

Il D-tor di una classe viene chiamato sostanzialmente alla fine dell'ambito, ma c'è un problema, ad esempio quando definiamo un'istanza sull'heap (allocazione dinamica), dovremmo eliminarla manualmente.

Non appena l'istruzione viene eseguita, viene chiamato il distruttore della classe base, ma non per quello derivato.

Un esempio pratico è quando, nel campo di controllo, devi manipolare attuatori, attuatori.

Alla fine del campo di applicazione, se non viene chiamato il distruttore di uno degli elementi di potenza (Attuatore), ci saranno conseguenze fatali.

#include <iostream>

class Mother{

public:

    Mother(){

          std::cout<<"Mother Ctor"<<std::endl;
    }

    virtual~Mother(){

        std::cout<<"Mother D-tor"<<std::endl;
    }


};

class Child: public Mother{

    public:

    Child(){

        std::cout<<"Child C-tor"<<std::endl;
    }

    ~Child(){

         std::cout<<"Child D-tor"<<std::endl;
    }
};

int main()
{

    Mother *c = new Child();
    delete c;

    return 0;
}

-1

Qualsiasi classe ereditata pubblicamente, polimorfica o meno, dovrebbe avere un distruttore virtuale. Per dirla in altro modo, se può essere indicato da un puntatore della classe base, la sua classe base dovrebbe avere un distruttore virtuale.

Se virtuale, viene chiamato il distruttore di classe derivato, quindi il costruttore della classe di base. Se non virtuale, viene chiamato solo il distruttore della classe base.


Direi che questo è necessario solo "se può essere indicato da un puntatore della classe base" e può essere eliminato pubblicamente. Ma immagino che non faccia male prendere l'abitudine di aggiungere medici virtuali nel caso in cui possano essere necessari in seguito.
underscore_d
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.