109 Stimmen

Wann sollten Sie keine virtuellen Destruktoren verwenden?

Gibt es jemals einen guten Grund für pas einen virtuellen Destruktor für eine Klasse deklarieren? Wann sollten Sie es ausdrücklich vermeiden, einen zu schreiben?

82voto

sep Punkte 3343

Es besteht keine Notwendigkeit, einen virtuellen Destruktor zu verwenden, wenn einer der folgenden Punkte zutrifft:

  • Keine Absicht, daraus Klassen abzuleiten
  • Keine Instanziierung auf dem Heap
  • Keine Absicht zur Speicherung mit Zugriff über einen Zeiger auf eine Oberklasse

Es gibt keinen besonderen Grund, es zu vermeiden, es sei denn, Sie haben wirklich einen großen Speicherbedarf.

0 Stimmen

Gute Übersicht. Ich habe meine Antwort zugunsten dieser Antwort fallen gelassen. +1 :)

32 Stimmen

Dies ist keine gute Antwort. "Es besteht keine Notwendigkeit" ist etwas anderes als "sollte nicht", und "keine Absicht" ist etwas anderes als "unmöglich gemacht".

5 Stimmen

Auch hinzufügen: keine Absicht, eine Instanz über einen Basisklassen-Zeiger zu löschen.

74voto

Richard Corden Punkte 20939

Um die Frage explizit zu beantworten, d.h. wann sollten Sie no einen virtuellen Destruktor deklarieren.

C++ '98/'03

Das Hinzufügen eines virtuellen Destruktors kann dazu führen, dass Ihre Klasse nicht mehr als POD (einfache alte Daten) * oder als Aggregat zu Nicht-POD. Dies kann dazu führen, dass Ihr Projekt nicht mehr kompiliert werden kann, wenn Ihr Klassentyp irgendwo als Aggregat initialisiert ist.

struct A {
  // virtual ~A ();
  int i;
  int j;
};
void foo () { 
  A a = { 0, 1 };  // Will fail if virtual dtor declared
}

Im Extremfall kann eine solche Änderung auch zu undefiniertem Verhalten führen, wenn die Klasse in einer Weise verwendet wird, die ein POD erfordert, z. B. bei der Übergabe über einen Ellipsis-Parameter oder bei der Verwendung mit memcpy.

void bar (...);
void foo (A & a) { 
  bar (a);  // Undefined behavior if virtual dtor declared
}

[Ein POD-Typ ist ein Typ, der bestimmte Garantien für sein Speicherlayout hat. Der Standard besagt eigentlich nur, dass, wenn man von einem Objekt mit POD-Typ in ein Array von Zeichen (oder Zeichen ohne Vorzeichen) und wieder zurück kopiert, das Ergebnis dasselbe sein wird wie das ursprüngliche Objekt.]

Modernes C++

In neueren Versionen von C++ wurde das Konzept des POD zwischen dem Klassenlayout und dessen Konstruktion, Kopieren und Zerstörung aufgeteilt.

Für den Fall der Ellipse ist es nicht mehr undefiniertes Verhalten, sondern wird nun bedingt unterstützt mit implementierungsdefinierter Semantik (N3937 - ~C++ '14 - 5.2.2/7):

...Die Übergabe eines potentiell bewerteten Arguments vom Typ Klasse (Klausel 9) mit einem nicht-trivialen Kopierkonstruktor, einem nicht-trivialen Verschiebekonstruktor oder einem nicht-trivialen Destruktor ohne entsprechenden Parameter wird mit implementierungsdefinierter Semantik bedingt unterstützt.

Die Deklaration eines anderen Destruktors als =default wird bedeuten, dass es nicht trivial ist (12.4/5)

... Ein Destruktor ist trivial, wenn er nicht vom Benutzer zur Verfügung gestellt wird ...

Andere Änderungen an Modern C++ verringern die Auswirkungen des Problems der Aggregatinitialisierung, da ein Konstruktor hinzugefügt werden kann:

struct A {
  A(int i, int j);
  virtual ~A ();
  int i;

  int j;
};
void foo () { 
  A a = { 0, 1 };  // OK
}

1 Stimmen

Sie haben recht, und ich habe mich geirrt, die Leistung ist nicht der einzige Grund. Aber das zeigt, dass ich mit dem Rest richtig lag: Der Programmierer der Klasse sollte besser Code einbauen, der verhindert, dass die Klasse jemals von jemand anderem geerbt wird.

0 Stimmen

Lieber Richard, kannst du bitte etwas mehr zu dem sagen, was du geschrieben hast. Ich verstehe deinen Punkt nicht, aber es scheint der einzige wertvolle Punkt zu sein, den ich beim Googeln gefunden habe. Oder kannst du vielleicht einen Link zu einer ausführlicheren Erklärung geben?

2 Stimmen

@JohnSmith Ich habe die Antwort aktualisiert. Hoffentlich hilft das.

30voto

Andy Punkte 2754

Ich deklariere einen virtuellen Destruktor, wenn und nur wenn ich virtuelle Methoden habe. Sobald ich virtuelle Methoden habe, vertraue ich nicht darauf, dass ich es vermeide, sie auf dem Heap zu instanziieren oder einen Zeiger auf die Basisklasse zu speichern. Beides sind sehr häufig vorkommende Operationen, die oft unbemerkt Ressourcen verschwinden lassen, wenn der Destruktor nicht als virtuell deklariert ist.

3 Stimmen

Und in der Tat gibt es eine Warnoption im gcc, die genau in diesem Fall warnt (virtuelle Methoden, aber kein virtueller dtor).

6 Stimmen

Besteht dann nicht die Gefahr, dass Speicherplatz verloren geht, wenn Sie von der Klasse ableiten, unabhängig davon, ob Sie andere virtuelle Funktionen haben?

1 Stimmen

Ich stimme mit mag. Die Verwendung eines virtuellen Destruktors und einer virtuellen Methode sind unterschiedliche Anforderungen. Virtuelle Destruktoren bieten einer Klasse die Möglichkeit, Aufräumarbeiten durchzuführen (z. B. Speicher löschen, Dateien schließen usw.) UND stellen außerdem sicher, dass die Konstruktoren aller ihrer Mitglieder aufgerufen werden.

7voto

Jay Conrod Punkte 27696

Ein virtueller Destruktor wird immer dann benötigt, wenn die Möglichkeit besteht, dass delete könnte auf einen Zeiger auf ein Objekt einer Unterklasse mit dem Typ Ihrer Klasse aufgerufen werden. Dadurch wird sichergestellt, dass der richtige Destruktor zur Laufzeit aufgerufen wird, ohne dass der Compiler die Klasse eines Objekts auf dem Heap zur Kompilierzeit kennen muss. Nehmen wir zum Beispiel an B ist eine Unterklasse von A :

A *x = new B;
delete x;     // ~B() called, even though x has type A*

Wenn Ihr Code nicht leistungskritisch ist, wäre es vernünftig, jeder Basisklasse, die Sie schreiben, einen virtuellen Destruktor hinzuzufügen, nur zur Sicherheit.

Wenn Sie jedoch feststellen, dass Sie delete Wenn man viele Objekte in einer engen Schleife aufruft, kann der Performance-Overhead des Aufrufs einer virtuellen Funktion (selbst einer leeren) spürbar sein. Der Compiler kann diese Aufrufe in der Regel nicht einbinden, und für den Prozessor ist es unter Umständen schwierig, vorherzusagen, wohin er gehen soll. Es ist unwahrscheinlich, dass dies einen signifikanten Einfluss auf die Leistung hat, aber es ist erwähnenswert.

0 Stimmen

"Wenn Ihr Code nicht leistungskritisch ist, wäre es vernünftig, jeder Basisklasse, die Sie schreiben, einen virtuellen Destruktor hinzuzufügen, nur zur Sicherheit" sollte in jeder Antwort, die ich sehe, mehr betont werden.

5voto

Steve Jessop Punkte 264569

Nicht alle C++-Klassen sind für die Verwendung als Basisklasse mit dynamischer Polymorphie geeignet.

Wenn Ihre Klasse für dynamische Polymorphie geeignet sein soll, muss ihr Destruktor virtuell sein. Darüber hinaus müssen alle Methoden, die eine Unterklasse möglicherweise überschreiben möchte (d. h. alle öffentlichen Methoden sowie möglicherweise einige intern verwendete geschützte Methoden), virtuell sein.

Wenn Ihre Klasse nicht für dynamische Polymorphie geeignet ist, sollte der Destruktor nicht als virtuell gekennzeichnet werden, da dies irreführend ist. Es ermutigt die Leute nur, Ihre Klasse falsch zu verwenden.

Hier ein Beispiel für eine Klasse, die sich nicht für dynamische Polymorphie eignet, selbst wenn ihr Destruktor virtuell wäre:

class MutexLock {
    mutex *mtx_;
public:
    explicit MutexLock(mutex *mtx) : mtx_(mtx) { mtx_->lock(); }
    ~MutexLock() { mtx_->unlock(); }
private:
    MutexLock(const MutexLock &rhs);
    MutexLock &operator=(const MutexLock &rhs);
};

Der ganze Sinn dieses Kurses ist es, sich auf den Stapel für RAII zu setzen. Wenn Sie Zeiger auf Objekte dieser Klasse weitergeben, ganz zu schweigen von deren Unterklassen, dann machen Sie etwas falsch.

3 Stimmen

Polymorphe Verwendung bedeutet nicht polymorphe Löschung. Es gibt viele Anwendungsfälle, in denen eine Klasse zwar virtuelle Methoden, aber keinen virtuellen Destruktor hat. Betrachten Sie ein typisches statisch definiertes Dialogfeld in so ziemlich jedem GUI-Toolkit. Das Elternfenster wird die Kindobjekte zerstören, und es kennt den genauen Typ eines jeden, aber alle Kindfenster werden auch polymorph in einer beliebigen Anzahl von Orten verwendet, wie z.B. Treffertests, Zeichnen, Zugänglichkeits-APIs, die den Text für Text-to-Speech-Engines abrufen, usw.

4 Stimmen

Stimmt, aber der Fragesteller möchte wissen, wann man einen virtuellen Destruktor gezielt vermeiden sollte. Für das von Ihnen beschriebene Dialogfeld ist ein virtueller Destruktor zwar sinnlos, aber IMO nicht schädlich. Ich bin mir nicht sicher, ob ich jemals ein Dialogfeld mit einem Basisklassenzeiger löschen muss - zum Beispiel könnte ich in Zukunft wollen, dass mein übergeordnetes Fenster seine Kindobjekte mit Hilfe von Fabriken erzeugt. Es ist also nicht eine Frage von zu vermeiden. virtuellen Destruktor, sondern nur, dass man sich nicht die Mühe machen sollte, einen zu haben. Ein virtueller Destruktor für eine Klasse, die nicht für eine Ableitung geeignet ist ist schädlich, weil es irreführend ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X