Wann werden virtuelle Destruktoren verwendet?


1486

Ich habe ein solides Verständnis für die meisten OO-Theorien, aber das einzige, was mich sehr verwirrt, sind virtuelle Destruktoren.

Ich dachte, dass der Destruktor immer aufgerufen wird, egal was und für jedes Objekt in der Kette.

Wann sollen sie virtuell gemacht werden und warum?


6
Siehe dies: Virtual Destructor
Naveen

146
Jeder Destruktor unten wird gerufen, egal was passiert. virtualstellt sicher, dass es oben statt in der Mitte beginnt.
Mooing Duck


@MooingDuck das ist etwas irreführender Kommentar.
Euri Pinhollow

1
@FranklinYu, es ist gut, dass du gefragt hast, weil ich jetzt kein Problem mit diesem Kommentar sehe (außer dem Versuch, in Kommentaren eine Antwort zu geben).
Euri Pinhollow

Antworten:


1572

Virtuelle Destruktoren sind nützlich, wenn Sie möglicherweise eine Instanz einer abgeleiteten Klasse über einen Zeiger auf die Basisklasse löschen:

class Base 
{
    // some virtual methods
};

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

Hier werden Sie feststellen, dass ich den Destruktor von Base nicht als Destruktor deklariert habe virtual. Schauen wir uns nun den folgenden Ausschnitt an:

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

Da Basis Destruktor nicht ist virtualund bist ein Base*Zeige auf ein DerivedObjekt, delete bhat nicht definiertes Verhalten :

[In delete b] Wenn sich der statische Typ des zu löschenden Objekts von seinem dynamischen Typ unterscheidet, muss der statische Typ eine Basisklasse des dynamischen Typs des zu löschenden Objekts sein und der statische Typ muss einen virtuellen Destruktor oder den haben Verhalten ist undefiniert .

In den meisten Implementierungen wird der Aufruf des Destruktors wie jeder nicht virtuelle Code aufgelöst, was bedeutet, dass der Destruktor der Basisklasse aufgerufen wird, jedoch nicht der der abgeleiteten Klasse, was zu einem Ressourcenleck führt.

Zusammenfassend lässt sich sagen, dass Basisklassen immer dann Destruktoren sind, virtualwenn sie polymorph manipuliert werden sollen.

Wenn Sie das Löschen einer Instanz durch einen Basisklassenzeiger verhindern möchten, können Sie den Basisklassendestruktor geschützt und nicht virtuell machen. Auf diese Weise lässt der Compiler Sie deletekeinen Basisklassenzeiger aufrufen .

In diesem Artikel von Herb Sutter erfahren Sie mehr über Virtualität und den Destruktor der virtuellen Basisklasse .


174
Dies würde erklären, warum ich in einer Fabrik, die ich zuvor hergestellt habe, massive Lecks hatte. Alles macht jetzt Sinn. Danke
Lodle

8
Nun, dies ist ein schlechtes Beispiel, da es keine Datenelemente gibt. Was passiert , wenn Baseund Derivedhaben alle automatischen Speichervariablen? dh es gibt keinen "speziellen" oder zusätzlichen benutzerdefinierten Code, der im Destruktor ausgeführt werden kann. Ist es dann in Ordnung, überhaupt keine Destruktoren mehr zu schreiben? Oder hat die abgeleitete Klasse immer noch einen Speicherverlust?
Bobobobo


28
Aus dem Artikel von Herb Sutter: "Richtlinie 4: Ein Basisklassen-Destruktor sollte entweder öffentlich und virtuell oder geschützt und nicht virtuell sein."
Eisbecher

3
Auch aus dem Artikel - "Wenn Sie polymorph ohne einen virtuellen Destruktor löschen, rufen Sie das gefürchtete Gespenst des" undefinierten Verhaltens "herbei, ein Gespenst, das ich persönlich lieber nicht einmal in einer mäßig gut beleuchteten Gasse treffen würde, vielen Dank." lol
Bondolin

219

Ein virtueller Konstruktor ist nicht möglich, aber ein virtueller Destruktor ist möglich. Lass uns experimentieren .......

#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;
}

Der obige Code gibt Folgendes aus:

Base Constructor Called
Derived constructor called
Base Destructor called

Die Konstruktion des abgeleiteten Objekts folgt der Konstruktionsregel, aber wenn wir den "b" -Zeiger (Basiszeiger) löschen, haben wir festgestellt, dass nur der Basisdestruktor aufgerufen wird. Das darf aber nicht passieren. Um das Richtige zu tun, müssen wir den Basis-Destruktor virtuell machen. Nun wollen wir sehen, was im Folgenden passiert:

#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;
}

Die Ausgabe hat sich wie folgt geändert:

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

Die Zerstörung des Basiszeigers (der eine Zuordnung für das abgeleitete Objekt erfordert!) Folgt also der Zerstörungsregel, dh zuerst der Abgeleiteten, dann der Basis. Auf der anderen Seite gibt es nichts Schöneres als einen virtuellen Konstruktor.


1
"Virtueller Konstruktor ist nicht möglich" bedeutet, dass Sie den virtuellen Konstruktor nicht selbst schreiben müssen. Die Konstruktion des abgeleiteten Objekts muss der Konstruktionskette von der abgeleiteten zur Basis folgen. Sie müssen also nicht das virtuelle Schlüsselwort für Ihren Konstruktor schreiben. Vielen Dank
Tunvir Rahman Tusher

4
@ Murkantilism, "virtuelle Konstruktoren können nicht gemacht werden" ist in der Tat wahr. Ein Konstruktor kann nicht als virtuell markiert werden.
Cmeub

1
@cmeub, aber es gibt eine Redewendung, um das zu erreichen, was Sie von einem virtuellen Konstruktor erwarten würden. Siehe parashift.com/c++-faq-lite/virtual-ctors.html
cape1232

@TunvirRahmanTusher Könnten Sie bitte erklären, warum der Basiszerstörer aufgerufen wird?
Rimalonfire

@ Rimiro Es ist automatisch von C ++. Sie können dem Link stackoverflow.com/questions/677620/…
Tunvir Rahman Tusher

195

Deklarieren Sie Destruktoren in polymorphen Basisklassen als virtuell. Dies ist Punkt 7 in Scott Meyers ' Effective C ++ . Meyers geht weiter zusammenfassen , dass , wenn eine Klasse hat jede virtuelle Funktion, sollte es einen virtuellen Destruktor haben, und dass die Klassen nicht - Basisklassen ausgelegt sein oder nicht verwendet werden entworfen polymorph sollte nicht virtuelle Destruktoren deklarieren.


14
+ "Wenn eine Klasse eine virtuelle Funktion hat, sollte sie einen virtuellen Destruktor haben, und dass Klassen, die nicht als Basisklassen oder nicht als polymorph verwendet konzipiert sind, keine virtuellen Destruktoren deklarieren sollten.": Gibt es Fälle, in denen dies sinnvoll ist? diese Regel brechen? Wenn nicht, wäre es sinnvoll, den Compiler diese Bedingung überprüfen zu lassen und einen Fehler auszugeben, wenn er nicht erfüllt ist?
Giorgio

@Giorgio Ich kenne keine Ausnahmen von der Regel. Aber ich würde mich nicht als C ++ - Experte bewerten, daher möchten Sie dies möglicherweise als separate Frage veröffentlichen. Eine Compiler-Warnung (oder eine Warnung von einem statischen Analysetool) ist für mich sinnvoll.
Bill the Lizard

10
Klassen können so gestaltet werden, dass sie nicht über den Zeiger eines bestimmten Typs gelöscht werden und dennoch virtuelle Funktionen haben - ein typisches Beispiel ist eine Rückrufschnittstelle. Man löscht seine Implementierung nicht über einen Callback-Interface-Zeiger, da dies nur zum Abonnieren dient, sondern virtuelle Funktionen hat.
Dascandy

3
@dascandy Genau - das oder alles vielen anderen Situationen, in denen wir polymorphes Verhalten verwenden, aber keine Speicherverwaltung über Zeiger durchführen - z. B. Objekte mit automatischer oder statischer Dauer verwalten, wobei Zeiger nur als Beobachtungsrouten verwendet werden. Keine Notwendigkeit / kein Zweck bei der Implementierung eines virtuellen Destruktors in solchen Fällen. Da wir hier nur Leute zitieren, bevorzuge ich Sutter von oben: "Richtlinie 4: Ein Basisklassen-Destruktor sollte entweder öffentlich und virtuell oder geschützt und nicht virtuell sein." Letzteres stellt sicher, dass jedem, der versehentlich versucht, über einen Basiszeiger zu löschen, der Fehler seines
underscore_d

1
@Giorgio Es gibt tatsächlich einen Trick, den man verwenden und einen virtuellen Aufruf eines Destruktors vermeiden kann: Binde über eine Konstante ein abgeleitetes Objekt an eine Basis, wie z const Base& = make_Derived();. In diesem Fall wird der Destruktor des DerivedWertes aufgerufen, auch wenn er nicht virtuell ist, sodass der durch vtables / vpointers verursachte Overhead gespart wird. Natürlich ist der Umfang ziemlich begrenzt. Andrei Alexandrescu erwähnte dies in seinem Buch Modern C ++ Design .
vsoftco

46

Beachten Sie auch, dass das Löschen eines Basisklassenzeigers, wenn kein virtueller Destruktor vorhanden ist, zu undefiniertem Verhalten führt . Etwas, das ich erst kürzlich gelernt habe:

Wie soll sich das Überschreiben von Löschen in C ++ verhalten?

Ich benutze C ++ seit Jahren und schaffe es immer noch, mich aufzuhängen.


Ich habe mir Ihre Frage angesehen und festgestellt, dass Sie den Basiszerstörer als virtuell deklariert haben. Bleibt "das Löschen eines Basisklassenzeigers, wenn kein virtueller Destruktor vorhanden ist, ein undefiniertes Verhalten" in Bezug auf Ihre Frage gültig? Da in dieser Frage beim Aufrufen von delete die abgeleitete Klasse (die von ihrem neuen Operator erstellt wurde) zuerst auf eine kompatible Version überprüft wird. Da es dort einen fand, wurde es genannt. Glauben Sie also nicht, dass es besser wäre zu sagen, dass "das Löschen eines Basisklassenzeigers, wenn kein Destruktor vorhanden ist, zu undefiniertem Verhalten führt"?
Ubuntugod

Das ist so ziemlich das Gleiche. Der Standardkonstruktor ist nicht virtuell.
BigSandwich

41

Machen Sie den Destruktor immer dann virtuell, wenn Ihre Klasse polymorph ist.


13

Aufruf des Destruktors über einen Zeiger auf eine Basisklasse

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

Der Aufruf des virtuellen Destruktors unterscheidet sich nicht von anderen virtuellen Funktionsaufrufen.

Denn base->f()der Anruf wird an weitergeleitet Derived::f(), und es ist dasselbe für base->~Base()- seine übergeordnete Funktion - dieDerived::~Derived() wird aufgerufen.

Gleiches passiert, wenn der Destruktor indirekt aufgerufen wird, z delete base;. Die deleteAnweisung wird aufgerufen, an base->~Base()die gesendet wird Derived::~Derived().

Abstrakte Klasse mit nicht virtuellem Destruktor

Wenn Sie ein Objekt nicht über einen Zeiger auf seine Basisklasse löschen möchten, ist kein virtueller Destruktor erforderlich. Machen Sie protectedes einfach so, dass es nicht versehentlich aufgerufen wird:

// 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.
}

Ist es notwendig, ~Derived()in allen abgeleiteten Klassen explizit zu deklarieren , auch wenn es nur so ist ~Derived() = default? Oder ist das durch die Sprache impliziert (was das Auslassen sicher macht)?
Ponkadoodle

@ Wallacoloo nein, deklariere es nur, wenn es nötig ist. ZB um einen protectedAbschnitt einzufügen oder um sicherzustellen, dass er virtuell ist, indem er verwendet override.
Abyx

9

Ich denke gerne über Schnittstellen und Implementierungen von Schnittstellen nach. In C ++ ist die Sprachschnittstelle eine reine virtuelle Klasse. Der Destruktor ist Teil der Schnittstelle und wird voraussichtlich implementiert. Daher sollte der Destruktor rein virtuell sein. Wie wäre es mit Konstruktor? Der Konstruktor ist eigentlich nicht Teil der Schnittstelle, da das Objekt immer explizit instanziiert wird.


2
Es ist eine andere Perspektive auf dieselbe Frage. Wenn wir in Schnittstellen anstelle von Basisklasse oder abgeleiteter Klasse denken, dann ist es eine natürliche Schlussfolgerung: Wenn es ein Teil der Schnittstelle ist, dann machen Sie es virtuell. Wenn nicht, nicht.
Dragan Ostojic

2
+1 für die Angabe der Ähnlichkeit des OO-Konzepts der Schnittstelle und einer rein virtuellen C ++ - Klasse . In Bezug auf Destruktor wird erwartet, implementiert zu werden : das ist oft unnötig. Sofern eine Klasse keine Ressource verwaltet, z. B. dynamisch zugewiesenen Rohspeicher (z. B. nicht über einen intelligenten Zeiger), ein Dateihandle oder ein Datenbankhandle, ist die Verwendung des vom Compiler erstellten Standarddestruktors in abgeleiteten Klassen in Ordnung. Beachten Sie, dass ein Destruktor (oder eine Funktion), der virtualin einer Basisklasse deklariert ist, automatisch virtualin einer abgeleiteten Klasse deklariert wird, auch wenn dies nicht deklariert ist.
DavidRR

Dabei fehlt das entscheidende Detail, dass der Destruktor nicht unbedingt Teil der Schnittstelle ist. Man kann leicht Klassen programmieren, die polymorphe Funktionen haben, die der Aufrufer aber nicht verwaltet / nicht löschen darf. Dann hat ein virtueller Destruktor keinen Zweck. Um dies sicherzustellen, sollte der nicht virtuelle - wahrscheinlich standardmäßige - Destruktor nicht öffentlich sein. Wenn ich raten müsste, würde ich sagen, dass solche Klassen häufiger intern für Projekte verwendet werden, aber das macht sie als Beispiel / Nuance in all dem nicht weniger relevant.
underscore_d

8

Das virtuelle Schlüsselwort für den Destruktor ist erforderlich, wenn verschiedene Destruktoren die richtige Reihenfolge einhalten sollen, während Objekte über den Basisklassenzeiger gelöscht werden. zum Beispiel:

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

Wenn Ihr Basisklassen-Destruktor virtuell ist, werden Objekte in einer Reihenfolge zerstört (zuerst abgeleitetes Objekt, dann Basis). Wenn Ihr Basisklassendestruktor NICHT virtuell ist, wird nur das Basisklassenobjekt gelöscht (da der Zeiger der Basisklasse "Base * myObj" entspricht). Es kommt also zu einem Speicherverlust für das abgeleitete Objekt.


7

Um es einfach zu machen: Der virtuelle Destruktor zerstört die Ressourcen in der richtigen Reihenfolge, wenn Sie einen Basisklassenzeiger löschen, der auf ein abgeleitetes Klassenobjekt zeigt.

 #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


Wenn der virtuelle Basiszerstörer nicht vorhanden ist und deleteein Basiszeiger aufgerufen wird, führt dies zu undefiniertem Verhalten.
James Adkison

@ JamesAdkison warum führt es zu undefiniertem Verhalten?
Rimalonfire

@ Rimiro Es ist, was der Standard sagt . Ich habe keine Kopie, aber der Link führt Sie zu einem Kommentar, in dem jemand auf den Ort innerhalb des Standards verweist.
James Adkison

@rimiro "Wenn das Löschen daher polymorph über die Basisklassenschnittstelle durchgeführt werden kann, muss es sich virtuell verhalten und virtuell sein. In der Tat erfordert die Sprache dies - wenn Sie polymorph ohne virtuellen Destruktor löschen, rufen Sie das gefürchtete Gespenst von auf "undefiniertes Verhalten", ein Gespenst, das ich persönlich lieber nicht einmal in einer mäßig gut beleuchteten Gasse treffen würde, vielen Dank. " ( gotw.ca/publications/mill18.htm ) - Herb Sutter
James Adkison

4

Destruktoren für virtuelle Basisklassen sind "Best Practice" - Sie sollten sie immer verwenden, um (schwer zu erkennende) Speicherlecks zu vermeiden. Mit ihnen können Sie sicher sein, dass alle Destruktoren in der Vererbungskette Ihrer Klassen aufgerufen werden (in der richtigen Reihenfolge). Durch das Erben von einer Basisklasse mithilfe eines virtuellen Destruktors wird der Destruktor der ererbenden Klasse ebenfalls automatisch virtuell (sodass Sie in der Deklaration des erbenden Klassendestruktors nicht erneut "virtuell" eingeben müssen).


4

Wenn Sie shared_ptr(nur shared_ptr, nicht unique_ptr) verwenden, muss der Destruktor der Basisklasse nicht virtuell sein:

#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());
}

Ausgabe:

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

Obwohl dies möglich ist, würde ich jeden davon abhalten, dies zu verwenden. Der Overhead eines virtuellen Destruktors ist winzig und dies macht es nur möglich, Fehler zu machen, insbesondere durch einen weniger erfahrenen Programmierer, der dies nicht weiß. Dieses kleine virtualSchlüsselwort könnte Sie vor viel Qual bewahren.
Michal Štein

3

Was ist ein virtueller Destruktor oder wie wird ein virtueller Destruktor verwendet?

Ein Klassendestruktor ist eine Funktion mit demselben Namen wie die mit ~ vorangestellte Klasse, die den von der Klasse zugewiesenen Speicher neu zuweist. Warum brauchen wir einen virtuellen Destruktor?

Siehe das folgende Beispiel mit einigen virtuellen Funktionen

Das Beispiel zeigt auch, wie Sie einen Buchstaben in einen oberen oder unteren konvertieren können

#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;
}

Aus dem obigen Beispiel können Sie ersehen, dass der Destruktor für die MakeUpper- und die MakeLower-Klasse nicht aufgerufen wird.

Siehe das nächste Beispiel mit dem virtuellen Destruktor

#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;
}

Der virtuelle Destruktor ruft explizit den am meisten abgeleiteten Laufzeit-Destruktor der Klasse auf, damit er das Objekt ordnungsgemäß löschen kann.

Oder besuchen Sie den Link

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


2

wenn Sie den abgeleiteten Klassendestruktor aus der Basisklasse aufrufen müssen. Sie müssen den Destruktor der virtuellen Basisklasse in der Basisklasse deklarieren.


2

Ich denke, der Kern dieser Frage betrifft virtuelle Methoden und Polymorphismus, nicht den Destruktor speziell. Hier ist ein klareres Beispiel:

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;
}

Wird ausgedruckt:

This is B.

Ohne wird virtuales ausgedruckt:

This is A.

Und jetzt sollten Sie verstehen, wann Sie virtuelle Destruktoren verwenden müssen.


Nein, dies überträgt nur die grundlegenden Grundlagen virtueller Funktionen und ignoriert völlig die Nuance, wann / warum der Destruktor einer sein sollte - was nicht so intuitiv ist, weshalb das OP die Frage gestellt hat. (Auch, warum die unnötige dynamische Zuordnung hier? Einfach tun B b{}; A& a{b}; a.foo();. Das Überprüfen auf NULL- was sein sollte nullptr- bevor delete- mit falscher Indendation - ist nicht erforderlich: delete nullptr;wird als No-Op definiert. Wenn überhaupt, sollten Sie dies vor dem Aufruf überprüft haben ->foo(), da sonst undefiniertes Verhalten auftreten kann, wenn das newirgendwie fehlschlägt.)
underscore_d

2
Es ist sicher, deleteeinen NULLZeiger aufzurufen (dh Sie brauchen den if (a != NULL)Wachmann nicht).
James Adkison

@ SegeshD Ja, ich weiß. Das habe ich in meinem Kommentar
James Adkison

1

Ich dachte, es wäre vorteilhaft, das "undefinierte" Verhalten oder zumindest das "abgestürzte" undefinierte Verhalten zu diskutieren, das beim Löschen durch eine Basisklasse (/ struct) ohne virtuellen Destruktor oder genauer gesagt ohne vtable auftreten kann. Der folgende Code listet einige einfache Strukturen auf (dasselbe gilt für Klassen).

#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;
*/
}

Ich schlage nicht vor, ob Sie virtuelle Destruktoren benötigen oder nicht, obwohl ich im Allgemeinen denke, dass es eine gute Praxis ist, sie zu haben. Ich möchte nur auf den Grund hinweisen, warum Sie möglicherweise abstürzen, wenn Ihre Basisklasse (/ struct) keine vtable und Ihre abgeleitete Klasse (/ struct) hat und Sie ein Objekt über eine Basisklasse (/ struct) löschen. Zeiger. In diesem Fall ist die Adresse, die Sie an die freie Routine des Heapspeichers übergeben, ungültig und somit der Grund für den Absturz.

Wenn Sie den obigen Code ausführen, sehen Sie deutlich, wann das Problem auftritt. Wenn sich dieser Zeiger der Basisklasse (/ struct) von diesem Zeiger der abgeleiteten Klasse (/ struct) unterscheidet, tritt dieses Problem auf. Im obigen Beispiel haben Struktur a und b keine vtables. Die Strukturen c und d haben vtables. Somit wird ein a- oder b-Zeiger auf eine ac- oder d-Objektinstanz festgelegt, um die vtable zu berücksichtigen. Wenn Sie diesen a- oder b-Zeiger zum Löschen übergeben, stürzt er ab, da die Adresse für die freie Routine des Heaps ungültig ist.

Wenn Sie abgeleitete Instanzen mit vtables aus Basisklassenzeigern löschen möchten, müssen Sie sicherstellen, dass die Basisklasse über eine vtable verfügt. Eine Möglichkeit, dies zu tun, besteht darin, einen virtuellen Destruktor hinzuzufügen, mit dem Sie möglicherweise ohnehin die Ressourcen ordnungsgemäß bereinigen möchten.


0

Eine grundlegende Definition virtualist, dass bestimmt wird, ob eine Elementfunktion einer Klasse in ihren abgeleiteten Klassen überschrieben werden kann.

Der D-Tor einer Klasse wird grundsätzlich am Ende des Bereichs aufgerufen. Es gibt jedoch ein Problem. Wenn wir beispielsweise eine Instanz auf dem Heap definieren (dynamische Zuordnung), sollten Sie sie manuell löschen.

Sobald der Befehl ausgeführt wird, wird der Basisklassen-Destruktor aufgerufen, jedoch nicht für den abgeleiteten.

Ein praktisches Beispiel ist, wenn Sie im Kontrollfeld Effektoren und Aktoren manipulieren müssen.

Wenn am Ende des Bereichs der Destruktor eines der Leistungselemente (Aktuator) nicht aufgerufen wird, hat dies fatale Konsequenzen.

#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

Jede Klasse, die öffentlich vererbt wird, polymorph oder nicht, sollte einen virtuellen Destruktor haben. Anders ausgedrückt, wenn ein Basisklassenzeiger darauf zeigen kann, sollte seine Basisklasse einen virtuellen Destruktor haben.

Wenn virtuell, wird der abgeleitete Klassendestruktor aufgerufen, dann der Basisklassenkonstruktor. Wenn nicht virtuell, wird nur der Basisklassen-Destruktor aufgerufen.


Ich würde sagen, dass dies nur notwendig ist, "wenn ein Basisklassenzeiger darauf verweisen kann " und öffentlich gelöscht werden kann. Aber ich denke, es tut nicht weh, sich daran zu gewöhnen, virtuelle Dtoren hinzuzufügen, falls sie später benötigt werden.
underscore_d
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.