c++ virtueller - Wann sollten virtuelle Destruktoren verwendet werden?





methoden virtual (14)


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

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

Siehe das folgende Beispiel mit einigen virtuellen Funktionen

Das Beispiel zeigt auch, wie Sie einen Buchstaben nach oben oder unten umwandeln 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 sehen, dass der Destruktor für MakeUpper und 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 abgeleiteten Laufzeitdestruktor 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

Ich habe ein solides Verständnis der meisten OO-Theorie, aber die eine Sache, die mich sehr verwirrt, ist 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?




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




Virtuelle Basisklassen-Destruktoren sind "best practice" - Sie sollten sie immer verwenden, um Speicherlecks zu vermeiden (schwer zu erkennen). Mit ihnen können Sie sicher sein, dass alle Destruktoren in der Vererbungskette Ihrer Klassen aufgerufen werden (in der richtigen Reihenfolge). Wenn Sie von einer Basisklasse mit virtuellem Destruktor erben, wird der Destruktor der erbenden Klasse automatisch ebenfalls virtuell (Sie müssen also in der Deklaration der erbenden Klasse destructor nicht "virtual" eingeben).




Ich denke, der Kern dieser Frage betrifft virtuelle Methoden und Polymorphie, nicht den 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;
}

Wird ausgedruckt:

This is B.

Ohne virtual es ausgedruckt:

This is A.

Und jetzt sollten Sie verstehen, wann virtuelle Destruktoren verwendet werden.




Ein virtuelles Schlüsselwort für den Destruktor ist erforderlich, wenn verschiedene Destruktoren der richtigen Reihenfolge folgen sollen, während Objekte durch den Basisklassenzeiger gelöscht werden. beispielsweise:

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

Wenn der abgeleitete Klassendestruktor virtuell ist, werden Objekte in einer Reihenfolge (zuerst abgeleitetes Objekt, dann Basis) gelöscht. Wenn der abgeleitete Klassendestruktor NICHT virtuell ist, wird nur das Basisklassenobjekt gelöscht (weil der Zeiger der Basisklasse "Base * myObj" entspricht). Es wird also ein Speicherleck für das abgeleitete Objekt geben.




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




Jede Klasse, die öffentlich, polymorph oder nicht, geerbt wird, sollte einen virtuellen Destruktor haben. Um es anders auszudrücken: Wenn auf einen Basisklassenzeiger verwiesen werden kann, sollte seine Basisklasse einen virtuellen Destruktor haben.

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




Destruktor über einen Zeiger auf eine Basisklasse aufrufen

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.

Für base->f() wird der Aufruf an Derived::f() base->~Base() , und das Gleiche gilt für base->~Base() - seine übergeordnete Funktion - der Derived::~Derived() wird aufgerufen.

Das Gleiche passiert, wenn der Destruktor indirekt aufgerufen wird, zB delete base; . Die delete Anweisung ruft base->~Base() die an Derived::~Derived() .

Abstrakte Klasse mit nicht virtuellem Destruktor

Wenn Sie ein Objekt nicht über einen Zeiger auf seine Basisklasse löschen, 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.
}



Um einfach zu sein, soll Virtual Destructor die Ressourcen in einer ordnungsgemäßen Reihenfolge zerstören, wenn Sie einen Basisklassenzeiger löschen, der auf das abgeleitete 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



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




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

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. Sehen wir uns nun das folgende Snippet an:

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

Da der Destruktor von Base nicht virtual und b eine Base* die auf ein Derived Objekt verweist, hat delete b ein undefiniertes Verhalten :

[In delete b ], wenn der statische Typ des zu löschenden Objekts sich von seinem dynamischen Typ unterscheidet, sollte der statische Typ eine Basisklasse des dynamischen Typs des zu löschenden Objekts und der statische Typ einen virtuellen Destruktor haben oder das Verhalten ist nicht definiert .

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

Zusammenfassend: Machen Sie die Destruktoren der Basisklassen immer virtual 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 festlegen. Auf diese Weise kann der Compiler das delete für einen Basisklassenzeiger nicht aufrufen.

In diesem Artikel von Herb Sutter können Sie mehr über Virtualität und virtuellen Basisklasse-Destruktor erfahren.




Deklarieren Destruktoren virtuell in polymorphen Basisklassen. Dies ist Artikel 7 in Scott Meyers ' Effective C ++ . Meyers fährt fort, dass, wenn eine Klasse eine virtuelle Funktion hat, sie einen virtuellen Destruktor haben sollte, und dass Klassen, die nicht als Basisklassen ausgelegt sind oder nicht für eine polymorphe Verwendung entworfen wurden, keine virtuellen Destruktoren deklarieren sollten.




Ich dachte, es wäre vorteilhaft, das "undefinierte" Verhalten oder zumindest das "crash" undefinierte Verhalten zu diskutieren, das beim Löschen durch eine Basisklasse (/ struct) ohne virtuellen Destruktor, oder genauer gesagt, keine 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 brauchen oder nicht, obwohl ich generell denke, dass es eine gute Übung ist, sie zu haben. Ich weise nur auf den Grund hin, der zu einem Absturz führen könnte, 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 Heaps übergeben, ungültig und daher der Grund für den Absturz.

Wenn Sie den obigen Code ausführen, werden Sie deutlich sehen, wenn das Problem auftritt. Wenn der this-Zeiger der Basisklasse (/ struct) sich vom this-Zeiger der abgeleiteten Klasse (/ struct) unterscheidet, werden Sie auf dieses Problem stoßen. Im obigen Beispiel haben Struktur a und b keine VTables. 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, weil die Adresse für die freie Routine des Heapspeichers ungültig ist.

Wenn Sie beabsichtigen, abgeleitete Instanzen mit vtables aus Basisklassenzeigern zu löschen, müssen Sie sicherstellen, dass die Basisklasse über eine vtable verfügt. Eine Möglichkeit besteht darin, einen virtuellen Destruktor hinzuzufügen, der auf jeden Fall Ressourcen ordnungsgemäß bereinigen soll.




Ich habe ein gleichwertiges C-Programm zum Experimentieren programmiert, und ich kann dieses merkwürdige Verhalten bestätigen. gcc glaubt gcc dass die 64-Bit-Ganzzahl (die sowieso eine size_t -Zahl sein sollte) besser ist, da die Verwendung von uint_fast32_t dazu uint_fast32_t dass gcc eine 64-Bit-Uint verwendet.

Ich habe ein bisschen mit der Versammlung herumgespielt:
Nehmen Sie einfach die 32-Bit-Version, ersetzen Sie alle 32-Bit-Anweisungen / -Register durch die 64-Bit-Version in der inneren Popcount-Schleife des Programms. Beobachtung: Der Code ist genauso schnell wie die 32-Bit-Version!

Dies ist offensichtlich ein Hack, da die Größe der Variablen nicht wirklich 64 Bit ist, da andere Teile des Programms immer noch die 32-Bit-Version verwenden, aber solange die innere Popcount-Schleife die Performance dominiert, ist dies ein guter Anfang .

Ich kopierte dann den inneren Schleifencode von der 32-Bit-Version des Programms, hackte ihn zu 64 Bit, fiedelte mit den Registern, um ihn als Ersatz für die innere Schleife der 64-Bit-Version zu verwenden. Dieser Code läuft auch so schnell wie die 32-Bit-Version.

Meine Schlussfolgerung ist, dass dies eine schlechte Instruktions-Planung durch den Compiler ist, nicht ein tatsächlicher Geschwindigkeits- / Latenz-Vorteil von 32-Bit-Instruktionen.

(Vorbehalt: Ich habe die Versammlung zerhackt, hätte etwas kaputt machen können, ohne es zu merken. Ich glaube nicht.)







c++ polymorphism virtual-destructor