Muss ich den virtuellen Basiszerstörer explizit aufrufen?


350

Wenn ich eine Klasse in C ++ überschreibe (mit einem virtuellen Destruktor), implementiere ich den Destruktor erneut als virtuell für die erbende Klasse, aber muss ich den Basisdestruktor aufrufen?

Wenn ja, stelle ich mir das so vor ...

MyChildClass::~MyChildClass() // virtual in header
{
    // Call to base destructor...
    this->MyBaseClass::~MyBaseClass();

    // Some destructing specific to MyChildClass
}

Habe ich recht?

Antworten:


469

Nein, Destruktoren werden automatisch in umgekehrter Reihenfolge aufgerufen. (Basisklassen zuletzt). Rufen Sie keine Basisklassen-Destruktoren auf.


Was ist mit reinen virtuellen Destruktoren? Mein Linker versucht, es am Ende des nicht virtuellen Destruktors meiner geerbten Klasse aufzurufen.
cjcurrie

40
Sie können keinen reinen virtuellen Destruktor ohne Körper haben. Gib ihm einfach einen leeren Körper. Bei einer regulären rein virtuellen Methode wird stattdessen die überschreibende Funktion aufgerufen. Bei Destruktoren werden alle aufgerufen, sodass Sie einen Body bereitstellen müssen. Das = 0 bedeutet nur, dass es überschrieben werden muss, also immer noch ein nützliches Konstrukt, wenn Sie es brauchen.
Lou Franco


Warum verursacht der Code von Nick Bolton keinen Segmentierungsfehler, obwohl er den Basisdestruktor zweimal aufruft, während das zweimalige Aufrufen deleteeines Zeigers auf die Basisklasse einen Segmentierungsfehler verursacht?
Maggyero

2
Ihnen wird kein Segmentierungsfehler mit falschem Code garantiert. Wenn Sie einen Destruktor aufrufen, wird kein Speicher freigegeben.
Lou Franco

92

Nein, Sie müssen den Basis-Destruktor nicht aufrufen. Ein Basis-Destruktor wird vom abgeleiteten Destruktor immer für Sie aufgerufen. Die Reihenfolge der Zerstörung finden Sie in meiner entsprechenden Antwort hier .

Um zu verstehen, warum Sie einen virtuellen Destruktor in der Basisklasse möchten, lesen Sie bitte den folgenden Code:

class B
{
public:
    virtual ~B()
    {
        cout<<"B destructor"<<endl;
    }
};


class D : public B
{
public:
    virtual ~D()
    {
        cout<<"D destructor"<<endl;
    }
};

Wenn Sie das tun:

B *pD = new D();
delete pD;

Wenn Sie dann keinen virtuellen Destruktor in B hätten, würde nur ~ B () aufgerufen. Da Sie jedoch einen virtuellen Destruktor haben, wird zuerst ~ D () und dann ~ B () aufgerufen.


20
Bitte geben Sie die Programmausgabe (Pseudo) an. es wird dem Leser helfen.
Kuldeep Singh Dhaka

@ KuldeepSinghDhaka Der Leser kann es live unter wandbox.org/permlink/KQtbZG1hjVgceSlO sehen .
das Schwein

27

Was die anderen gesagt haben, aber auch beachten, dass Sie den Destruktor in der abgeleiteten Klasse nicht als virtuell deklarieren müssen. Sobald Sie einen Destruktor virtuell deklarieren, wie Sie es in der Basisklasse tun, sind alle abgeleiteten Destruktoren virtuell, unabhängig davon, ob Sie sie deklarieren oder nicht. Mit anderen Worten:

struct A {
   virtual ~A() {}
};

struct B : public A {
   virtual ~B() {}   // this is virtual
};

struct C : public A {
   ~C() {}          // this is virtual too
};

1
Was ist, wenn ~ B nicht als virtuell deklariert ist? Ist ~ C noch virtuell?
Will

5
Ja. Wenn eine virtuelle Methode (eine beliebige, nicht nur der Destruktor) als virtuell deklariert wird, sind alle Überschreibungen dieser Methode in abgeleiteten Klassen automatisch virtuell. In diesem Fall ist es auch dann noch ~ C, wenn Sie ~ B nicht als virtuell deklarieren, und ~ C.
Boycy

1
Aber im Gegensatz zu anderen überschriebenen Methoden, die denselben Namen und dieselben Parameter wie die entsprechenden Methoden in der Basisklasse haben, ist der Name des Destruktors unterschiedlich. Ist das wichtig? @boycy
Yuan Wen

1
@YuanWenn dies nicht der Fall ist, überschreibt der (einzige) abgeleitete Destruktor immer den (einzigen) Destruktor seiner Basisklasse.
Boycy

10

Nein. Im Gegensatz zu anderen virtuellen Methoden, bei denen Sie die Base-Methode explizit aus der Ableitung aufrufen würden, um den Aufruf zu "verketten", generiert der Compiler Code, um die Destruktoren in der umgekehrten Reihenfolge aufzurufen, in der ihre Konstruktoren aufgerufen wurden.


9

Nein, Sie nennen den Basisklassen-Destruktor nie, er wird immer automatisch aufgerufen, wie andere darauf hingewiesen haben, aber hier ist ein Proof of Concept mit Ergebnissen:

class base {
public:
    base()  { cout << __FUNCTION__ << endl; }
    ~base() { cout << __FUNCTION__ << endl; }
};

class derived : public base {
public:
    derived() { cout << __FUNCTION__ << endl; }
    ~derived() { cout << __FUNCTION__ << endl; } // adding call to base::~base() here results in double call to base destructor
};


int main()
{
    cout << "case 1, declared as local variable on stack" << endl << endl;
    {
        derived d1;
    }

    cout << endl << endl;

    cout << "case 2, created using new, assigned to derive class" << endl << endl;
    derived * d2 = new derived;
    delete d2;

    cout << endl << endl;

    cout << "case 3, created with new, assigned to base class" << endl << endl;
    base * d3 = new derived;
    delete d3;

    cout << endl;

    return 0;
}

Die Ausgabe ist:

case 1, declared as local variable on stack

base::base
derived::derived
derived::~derived
base::~base


case 2, created using new, assigned to derive class

base::base
derived::derived
derived::~derived
base::~base


case 3, created with new, assigned to base class

base::base
derived::derived
base::~base

Press any key to continue . . .

Wenn Sie den Destruktor der Basisklasse als virtuell festlegen, was der Fall sein sollte, stimmen die Ergebnisse von Fall 3 mit denen von Fall 1 und 2 überein.


Gute Illustration. Wenn Sie versuchen, den Basisklassendestruktor aus der abgeleiteten Klasse aufzurufen, sollte ein Compilerfehler ähnlich dem Fehler "Fehler: Keine Übereinstimmungsfunktion für den Aufruf von 'BASE :: BASE ()' <newline> ~ BASE ();" angezeigt werden. Zumindest ist dies das Verhalten meines g ++ 7.x-Compilers.
Kemin Zhou


1

Destruktoren in C ++ werden automatisch nur dann in der Reihenfolge ihrer Konstruktionen (Abgeleitet, dann Basis) aufgerufen, wenn der Destruktor der Basisklasse deklariert istvirtual .

Wenn nicht, wird zum Zeitpunkt des Löschens des Objekts nur der Basisklassen-Destruktor aufgerufen.

Beispiel: Ohne virtuellen Destruktor

#include <iostream>

using namespace std;

class Base{
public:
  Base(){
    cout << "Base Constructor \n";
  }

  ~Base(){
    cout << "Base Destructor \n";
  }

};

class Derived: public Base{
public:
  int *n;
  Derived(){
    cout << "Derived Constructor \n";
    n = new int(10);
  }

  void display(){
    cout<< "Value: "<< *n << endl;
  }

  ~Derived(){
    cout << "Derived Destructor \n";
  }
};

int main() {

 Base *obj = new Derived();  //Derived object with base pointer
 delete(obj);   //Deleting object
 return 0;

}

Ausgabe

Base Constructor
Derived Constructor
Base Destructor

Beispiel: Mit Base Virtual Destructor

#include <iostream>

using namespace std;

class Base{
public:
  Base(){
    cout << "Base Constructor \n";
  }

  //virtual destructor
  virtual ~Base(){
    cout << "Base Destructor \n";
  }

};

class Derived: public Base{
public:
  int *n;
  Derived(){
    cout << "Derived Constructor \n";
    n = new int(10);
  }

  void display(){
    cout<< "Value: "<< *n << endl;
  }

  ~Derived(){
    cout << "Derived Destructor \n";
    delete(n);  //deleting the memory used by pointer
  }
};

int main() {

 Base *obj = new Derived();  //Derived object with base pointer
 delete(obj);   //Deleting object
 return 0;

}

Ausgabe

Base Constructor
Derived Constructor
Derived Destructor
Base Destructor

Es wird empfohlen, den Basisklassen-Destruktor zu deklarieren, da dies virtualsonst zu undefiniertem Verhalten führt.

Referenz: Virtueller Destruktor

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.