it-swarm.com.de

Wann werden virtuelle Destruktoren verwendet?

Ich habe ein solides Verständnis der 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 soll man sie virtuell machen und warum?

1381
Lodle

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

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 virtual deklariert habe. Schauen wir uns nun den folgenden Ausschnitt an:

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

Da der Destruktor von Base nicht virtual und b ein Base* ist, der auf ein Derived -Objekt verweist, hat delete bndefiniertes Verhalten :

[In delete b], wenn der statische Typ des zu löschenden Objekts von seinem dynamischen Typ abweicht, muss der statische Typ eine Basisklasse des dynamischen Typs des zu löschenden Objekts sein und Der statische Typ soll einen virtuellen Destruktor haben oder das Verhalten ist undefiniert .

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

Zusammenfassend lässt sich sagen, dass die Destruktoren der Basisklassen immer dann virtual sind, wenn sie polymorph manipuliert werden sollen.

Wenn Sie das Löschen einer Instanz durch einen Basisklassenzeiger verhindern möchten, können Sie den Basisklassendestruktor als geschützt und nicht virtuell definieren. Auf diese Weise lässt der Compiler nicht zu, dass Sie delete für einen Basisklassenzeiger aufrufen.

Weitere Informationen zu Virtualität und Destruktor für virtuelle Basisklassen finden Sie in dieser Artikel von Herb Sutter .

1476
Luc Touraille

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 Basiszerstörer aufgerufen wird. Das darf aber nicht passieren. Um das Passende 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 änderte sich wie folgt:

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

Die Zerstörung des Basiszeigers (der eine Zuordnung zu einem abgeleiteten Objekt benötigt!) Folgt also der Zerstörungsregel, d. H. Zuerst der abgeleiteten, dann der Basis. Auf der anderen Seite gibt es nichts Schöneres als einen virtuellen Konstruktor.

190

Deklarieren Sie Destruktoren als virtuell in polymorphen Basisklassen. Dies ist Punkt 7 in Scott Meyers ' Effective C++ . Meyers fasst zusammen, dass eine Klasse, wenn sie eine virtuelle Funktion hat, einen virtuellen Destruktor haben sollte und dass Klassen nicht als Basisklassen konzipiert sind oder nicht entworfen, um polymorph verwendet zu werden, sollte keine virtuellen Destruktoren deklarieren.

184
Bill the Lizard

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

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

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

43
BigSandwich

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

38
yesraaj

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 virtuelle Destruktor-Aufruf unterscheidet sich nicht von anderen virtuellen Funktionsaufrufen.

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

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

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. Mach es einfach protected, damit 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.
}
12
Abyx

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

9
Dragan Ostojic

Einfach gesagt, besteht der virtuelle Destruktor darin, die Ressourcen in der richtigen Reihenfolge zu zerstören, wenn Sie einen Basisklassenzeiger löschen, der auf ein abgeleitetes Klassenobjekt verweist.

 #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

6
Prakash GiBBs

Das virtuelle Schlüsselwort für den Destruktor ist erforderlich, wenn unterschiedliche 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 abgeleiteter Klassendestruktor virtuell ist, werden Objekte in einer Reihenfolge (zuerst abgeleitetes Objekt, dann Basis) zerstört. Wenn Ihr abgeleiteter Klassendestruktor NICHT virtuell ist, wird nur das Basisklassenobjekt gelöscht (da der Zeiger von der Basisklasse "Base * myObj" ist). Es kommt also zu einem Speicherverlust für abgeleitete Objekte.

5
Mukul Kashmira

Destruktoren für virtuelle Basisklassen sind "Best Practice" - Sie sollten sie immer verwenden, um (schwer zu erkennende) Speicherverluste zu vermeiden. Mit ihnen können Sie sicher sein, dass alle Destruktoren in der Vererbungskette Ihrer Klassen aufgerufen werden (in der richtigen Reihenfolge). Durch die Vererbung von einer Basisklasse mit dem virtuellen Destruktor wird der Destruktor der vererbenden Klasse automatisch auch virtuell.

3
Trantor

Was ist ein virtueller Destruktor oder wie verwendet man einen virtuellen Destruktor?

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

Das folgende Beispiel zeigt einige virtuelle Funktionen

Das Beispiel zeigt auch, wie Sie einen Buchstaben nach oben oder unten 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 erkennen, dass der Destruktor für die MakeUpper- und die MakeLower-Klasse nicht aufgerufen wird.

Sehen Sie sich das nächste Beispiel mit dem virtuellen Destruktor an

#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 häufigsten abgeleiteten Laufzeitdestruktor der Klasse auf, damit das Objekt ordnungsgemäß gelöscht werden kann.

Oder besuchen Sie den Link

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

3
user2578542

Ich dachte, es wäre vorteilhaft, das "undefinierte" Verhalten oder zumindest das "Absturz" -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 (das gleiche 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 weise nur auf den Grund hin, warum Sie möglicherweise abstürzen, wenn Ihre Basisklasse (/ struct) keine vtable hat und Ihre abgeleitete Klasse (/ struct) dies tut und Sie ein Objekt über eine Basisklasse (/ struct) löschen. Zeiger. In diesem Fall ist die Adresse, die Sie an die freie Routine des Heap übergeben, ungültig und daher der Grund für den Absturz.

Wenn Sie den obigen Code ausführen, sehen Sie deutlich, wann das Problem auftritt. Wenn sich der this-Zeiger der Basisklasse (/ struct) von dem this-Zeiger der abgeleiteten Klasse (/ struct) unterscheidet, tritt dieses Problem auf. In dem obigen Beispiel haben struct a und b keine vtables. Die Strukturen c und d haben vtables. Somit wird ein a- oder b-Zeiger auf eine c- oder d-Objektinstanz festgelegt, um die vtable zu berücksichtigen. Wenn Sie diesen Zeiger a oder b 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, den Sie möglicherweise trotzdem benötigen, um Ressourcen ordnungsgemäß zu bereinigen.

1
nickdu

Ich denke, der Kern dieser Frage sind virtuelle Methoden und Polymorphismus, nicht der Destruktor. 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;
}

Druckt aus:

This is B.

Ohne virtual wird gedruckt:

This is A.

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

1
gonjay

Wenn Sie shared_ptr verwenden (nur shared_ptr, nicht unique_ptr), 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
0
Zhenxiao Hao

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

0
user2641018