Frage

Ich verstehe die Notwendigkeit für einen virtuellen Destruktor. Aber warum brauchen wir einen rein virtuellen destructor? In einem der C ++ Artikel hat der Autor erwähnt, dass wir rein virtuellen Destruktor verwenden, wenn wir eine Klasse abstrakt machen wollen.

Aber wir können, indem sie eine der Elementfunktionen als reine virtuelle eine Klasse abstrakt machen.

Also meine Fragen sind

  1. Wann machen wir wirklich ein destructor rein virtuell? Kann jemand eine gute Echtzeit Beispiel geben?

  2. Wenn wir abstrakte Klassen schaffen ist es eine gute Praxis, die destructor auch rein virtuell zu machen? Wenn yes..then warum?

War es hilfreich?

Lösung

  1. Wahrscheinlich der wahre Grund, dass rein virtuelle Destruktoren erlaubt sind, ist, dass sie zu verbieten, würde bedeuten, ein andere Regel für die Sprache hinzufügen und es gibt keine Notwendigkeit für diese Regel, da keine negativen Auswirkungen von so dass ein rein virtuellen destructor kommen können.

  2. Nein, plain old virtuell ist genug.

Wenn Sie ein Objekt mit Standardimplementierungen für seine virtuellen Methoden erstellen und wollen es abstrakt machen, ohne dass jemand zwingt jede spezifische außer Kraft zu setzen Methode können Sie die destructor rein virtuelle machen. Ich sehe nicht viel Sinn, es aber es ist möglich.

Beachten Sie, dass da der Compiler eine implizite destructor für abgeleitete Klassen generieren, wenn der Autor der Klasse nicht so tun, werden alle abgeleiteten Klassen wird nicht sein abstrakt. Deshalb mit dem rein virtuellen Destruktor in der Basisklasse wird keinen Unterschied für die abgeleiteten Klassen machen. Es wird nur die Basisklasse abstrakt machen (danke für @kappa 's Kommentar).

Man kann auch davon ausgehen, dass jede Ableitung Klasse wahrscheinlich haben, müßte spezifischen up clean-Code und verwenden Sie den rein virtuellen destructor als Erinnerung zu schreiben, aber diese erdacht scheinen (und unenforced).

Hinweis: Der destructor ist die einzige Methode, auch wenn es ist rein virtuelle hat eine Implementierung haben, um zu abgeleiteten Klassen instanziiert (ja rein virtuelle Funktionen können Implementierungen haben).

struct foo {
    virtual void bar() = 0;
};

void foo::bar() { /* default implementation */ }

class foof : public foo {
    void bar() { foo::bar(); } // have to explicitly call default implementation.
};

Andere Tipps

Alles, was Sie für eine abstrakte Klasse müssen mindestens eine rein virtuelle Funktion. Jede Funktion wird tun; aber wie es geschieht, ist der destructor etwas, das jeder Klasse hat-so ist es immer dort als Kandidat. Darüber hinaus macht den destructor rein virtuelle (im Gegensatz zu nur virtuell) hat keine Verhaltensnebenwirkungen, andere als die Klasse abstrakt zu machen. Als solches empfehlen eine Menge von Styleguides, dass die rein virtuelle destuctor konsequent verwendet, um anzuzeigen, dass eine Klasse abstrakt-wenn aus keinem anderen Grund, als es eine einheitliche Stelle jemand liest den Code liefert schauen, um zu sehen, ob die Klasse abstrakt ist.

Wenn Sie eine abstrakte Basisklasse erstellen:

  • , die kann nicht instanziert werden (yep, dies ist redundant mit dem Begriff "abstrakt"!)
  • und muss virtuellen Destruktor Verhalten (Sie beabsichtigen vielmehr auf die ABC-Zeiger zu tragen als Zeiger auf die abgeleiteten Typen und Löschen durch sie)
  • und braucht keine anderen virtuellen Dispatch Verhalten für andere Methoden (vielleicht gibt es sind keine anderen Methoden? Betrachten wir ein einfaches geschützt „Ressource“ Container, der eine Konstrukteurs braucht / destructor / Zuordnung aber nicht viel mehr)

... ist es am einfachsten, die Klasse abstrakt zu machen, indem die destructor rein virtuellen machen und eine Definition (Methode Körper) für sie bereitstellt.

Für unsere hypothetische ABC:

Sie garantieren, dass es nicht instanziiert werden kann (auch intern in der Klasse selbst, deshalb private Konstruktoren nicht genug sein kann), erhalten Sie das virtuelle Verhalten, das Sie für die destructor wollen, und Sie müssen nicht eine andere finden und markieren Methode, die keine virtuellen Dispatch als „virtuelle“.

braucht

Aus den Antworten, die ich auf Ihre Frage gelesen habe, kann ich nicht einen guten Grund ableiten, um tatsächlich einen rein virtuellen destructor zu verwenden. Zum Beispiel hat der folgende Grund überzeugt mich nicht:

  

Wahrscheinlich der wahre Grund, dass rein virtuelle Destruktoren erlaubt sind, ist, dass sie zu verbieten, würden bedeuten, ein andere Regel für die Sprache hinzufügen und es gibt keine Notwendigkeit für diese Regel, da keine negativen Auswirkungen von so dass ein rein virtuellen destructor kommen können.

Meiner Meinung nach sind rein virtuelle Destruktoren können nützlich sein. Zum Beispiel annehmen, dass Sie zwei Klassen myClassA und myClassB in Ihrem Code haben, und dass myClassB erbt von myClassA. Aus den Gründen, von Scott Meyers in seinem Buch erwähnt „More Effective C ++“, Punkt 33 „Making Nicht-Blatt-Klassen abstrakt“, ist es besser, die Praxis tatsächlich eine abstrakte Klasse myAbstractClass zu erstellen, aus denen myClassA und myClassB erben. Dies ermöglicht eine bessere Abstraktion und verhindert, dass einige Probleme, die sich mit zum Beispiel Objektkopien.

In dem Abstraktionsprozess (der Schaffung Klasse myAbstractClass), kann es sein, dass keine Methode der myClassA oder myClassB ist ein guter Kandidat für eine rein virtuelle Methode ist (das ist eine Voraussetzung für myAbstractClass abstrakt zu sein). In diesem Fall definieren Sie die destructor abstrakte Klasse rein virtuell.

Im folgenden wird ein konkretes Beispiel von einem Code, den ich habe ich geschrieben. Ich habe zwei Klassen, Numerik / PhysicsParams, die gemeinsame Eigenschaften. Ich deshalb läßt sich von der abstrakten Klasse IParams erben. In diesem Fall hatte ich absolut keine Methode zur Hand, die rein virtuell sein könnte. Die setParameter Verfahren, zum Beispiel müssen den gleichen Körper für jede Unterklasse haben. Die einzige Wahl, die ich war musste IParams' destructor rein virtuelle zu machen.

struct IParams
{
    IParams(const ModelConfiguration& aModelConf);
    virtual ~IParams() = 0;

    void setParameter(const N_Configuration::Parameter& aParam);

    std::map<std::string, std::string> m_Parameters;
};

struct NumericsParams : IParams
{
    NumericsParams(const ModelConfiguration& aNumericsConf);
    virtual ~NumericsParams();

    double dt() const;
    double ti() const;
    double tf() const;
};

struct PhysicsParams : IParams
{
    PhysicsParams(const N_Configuration::ModelConfiguration& aPhysicsConf);
    virtual ~PhysicsParams();

    double g()     const; 
    double rho_i() const; 
    double rho_w() const; 
};

Wenn Sie instanziierenden die Basisklasse stoppen wollen, ohne eine Änderung machen in Ihrer bereits implementiert und getestet derive Klasse implementieren Sie einen rein virtuellen destructor in Ihrer Basisklasse.

Hier möchte ich sagen, wenn wir brauchen virtuellen Destruktor und wenn wir brauchen rein virtuelle destructor

class Base
{
public:
    Base();
    virtual ~Base() = 0; // Pure virtual, now no one can create the Base Object directly 
};

Base::Base() { cout << "Base Constructor" << endl; }
Base::~Base() { cout << "Base Destructor" << endl; }


class Derived : public Base
{
public:
    Derived();
    ~Derived();
};

Derived::Derived() { cout << "Derived Constructor" << endl; }
Derived::~Derived() {   cout << "Derived Destructor" << endl; }


int _tmain(int argc, _TCHAR* argv[])
{
    Base* pBase = new Derived();
    delete pBase;

    Base* pBase2 = new Base(); // Error 1   error C2259: 'Base' : cannot instantiate abstract class
}
  1. Wenn Sie möchten, dass niemand in der Lage sein sollte, das Objekt der Basisklasse direkt zu erstellen, verwenden Sie rein virtuellen destructor virtual ~Base() = 0. Normalerweise at-dest eine rein virtuelle Funktion erforderlich ist, nehmen wir virtual ~Base() = 0, wie diese Funktion.

  2. Wenn Sie oben Sache nicht brauchen, nur müssen Sie die sichere Vernichtung von abgeleiteten Klasse Objekt

    Basis * pBase = new Derived (); löschen pBase; rein virtueller Destruktor nicht erforderlich ist, nur virtueller destructor wird die Arbeit machen.

Sie sind immer in hypothetisch mit diesen Antworten, also werde ich versuchen, aus Gründen der Klarheit willen eine einfacheren, mehr auf der Erde Erklärung zu machen.

Die grundlegenden Beziehungen der objektorientierten Design sind zwei:  IS-A und HAS-A. Ich habe machen diejenigen, die nicht nach oben. Das ist, was sie genannt werden.

IS-A zeigt an, dass ein bestimmtes Objekt identifiziert die Klasse als die über ihn in einer Klassenhierarchie ist. Eine Banane Objekt ist eine Frucht Objekt, wenn es sich um eine Unterklasse der Frucht-Klasse ist. Dies bedeutet, dass überall eine Frucht-Klasse verwendet werden kann, kann eine Banane verwendet werden. Es ist nicht reflexiv, though. Sie können nicht eine Basisklasse für eine bestimmte Klasse ersetzen, wenn diese bestimmte Klasse für aufgerufen wird.

Hat-a angegeben, dass ein Objekt Teil eines zusammengesetzten Klasse ist und dass es ein Eigentumsverhältnis. Es bedeutet in C ++, dass es ein Mitgliedsobjekt ist und als solcher der Verantwortung liegt bei der besitzenden Klasse vor zerstörenden selbst zu entsorgen davon oder Hand Eigentum.

Diese beiden Konzepte sind leichter in Single-Vererbung Sprachen zu realisieren als in einer Mehrfachvererbung Modell wie C ++, aber die Regeln sind im Wesentlichen gleich. Die Komplikation kommt, wenn die Klassenidentität nicht eindeutig ist, wie eine Banane Klasse Zeiger in eine Funktion übergeben, die eine Frucht Klasse Zeiger nehmen.

Virtuelle Funktionen sind zum einen eine Laufzeit Sache. Es ist Teil des Polymorphismus, dass es verwendet wird, zu entscheiden, welche zu dem Zeitpunkt laufen funktionieren sie im laufenden Programm aufgerufen wird.

Das virtuelle Schlüsselwort ist eine Compiler-Direktive Funktionen in einer bestimmten Reihenfolge zu binden, wenn es Unklarheiten über die Klassenidentität ist. Virtuelle Funktionen sind immer in der übergeordneten Klassen (soweit ich weiß) und zeigen an den Compiler, die ihren Namen von Elementfunktionen Bindung sollte zunächst mit der Unterklasse Funktion übernehmen und der übergeordneten Klasse Funktion nach.

Eine Frucht Klasse könnte eine virtuelle Funktion Farbe () hat, die „NONE“ standardmäßig zurück. Die Bananen-Klasse Farbe () Funktion gibt „GELB“ oder „braun“.

Aber wenn die Funktion eine Frucht Zeiger ruft Farbe () auf der Banana Klasse, die an ihn gesendet - welche Farbe () Funktion aufgerufen wird? Die Funktion würde normalerweise rufen Obst :: Farbe () für eine Frucht Objekt.

Das würde 99% der Zeit nicht das, was beabsichtigt war. Aber wenn Obst :: Farbe () virtual deklariert wurde dann Banane: Farbe () würde für das Objekt aufgerufen werden, da die richtige Farbe () Funktion auf das Obst Zeiger zum Zeitpunkt des Anrufs gebunden wäre. Die Laufzeit wird überprüfen, was den Zeiger auf dem Objekt, weil es in der Fruit Klassendefinition virtueller markiert.

Dies ist anders als eine Funktion in einer Unterklasse überschreibt. In diesem Fall die Frucht Zeiger rufen Obst :: Farbe (), wenn alle es weiß, ist, dass es IS-Ein Zeiger auf Obst.

So, jetzt auf die Idee einer „rein virtuelle Funktion“ aufkommt. Es ist ein eher unglücklicher Begriff als Reinheit nichts damit zu tun hat. Es bedeutet, dass es beabsichtigt ist, dass die Basisklasse Methode nie aufgerufen werden. Tatsächlich eine rein virtuelle Funktion kann nicht aufgerufen werden. Es muss jedoch noch festgelegt werden. Eine Funktion Unterschrift muss vorhanden sein. Viele Programmierer machen eine leere Implementierung {} auf Vollständigkeit, aber der Compiler wird ein intern generieren, wenn nicht. In diesem Fall, wenn die Funktion aufgerufen wird, selbst wenn der Zeiger auf Frucht ist, Banane :: Farbe () wird aufgerufen, da es die einzige Implementierung von Farbe ist () gibt es.

Nun das letzte Stück des Puzzles. Konstruktoren und Destruktoren

reine virtuelle Konstruktoren sind illegal, vollständig. Das ist gerade aus.

Aber rein virtuelle Destruktoren im Fall funktionieren, dass Sie die Erstellung einer Basisklasse Instanz verbieten wollen. Es können nur Unterklassen instanziert werden, wenn der Destruktor der Basisklasse virtuell rein ist. Die Konvention ist es auf 0 zuweisen.

 virtual ~Fruit() = 0;  // pure virtual 
 Fruit::~Fruit(){}      // destructor implementation

Sie haben eine Implementierung in diesem Fall zu erstellen. Der Compiler weiß das ist, was Sie tun, und stellt sicher, dass Sie es richtig machen, oder esbeschwert sich mächtig, dass es nicht auf alle Funktionen benötigt verknüpfen kann es zu kompilieren. Die Fehler können verwirrend sein, wenn Sie nicht auf dem richtigen Weg sind, wie Sie Ihre Klassenhierarchie modellieren.

Sie sind also in diesem Fall verboten Instanzen von Obst zu schaffen, aber erlaubten Instanzen von Banana zu erstellen.

Ein Aufruf der Fruit Zeiger zu löschen, die auf eine Instanz von Banana Punkte Banana rufen :: ~ Banana () und dann Fuit nennen :: ~ Fruit (), immer. Denn egal was passiert, wenn Sie eine Unterklasse destructor aufrufen, müssen die Basisklasse destructor folgen.

Ist es ein schlechtes Modell? Es ist komplizierter in der Entwurfsphase, ja, aber es kann, dass die korrekte Verknüpfung gewährleisten während der Laufzeit durchgeführt wird, und dass eine Unterklasse Funktion ausgeführt wird, wenn Unklarheit, wie genau ist die Unterklasse zugegriffen wird.

Wenn Sie C ++ schreiben, so dass Sie nur um genaue Klasse Zeiger ohne Generika noch mehrdeutig Zeiger übergeben, dann virtuelle Funktionen sind nicht wirklich nötig. Aber wenn Sie Laufzeitflexibilität Typen erfordern (wie in Apfel Banane orange ==> Obst) Funktionen werden einfacher und vielseitiger mit weniger redundanten Code. Sie müssen nicht mehr eine Funktion für jede Art von Obst zu schreiben, und Sie wissen, dass jede Frucht Farbe () mit einer eigenen korrekten Funktion reagiert.

Ich hoffe, die langatmige Erklärung das Konzept erstarrt und nicht Dinge verwechselt. Es gibt viele gute Beispiele gibt, zu sehen, und genug aussehen und sie tatsächlich und Chaos mit ihnen laufen und Sie werden es bekommen.

Sie haben gefragt, für ein Beispiel, und ich glaube, die folgende einen Grund für einen rein virtuellen destructor bietet. Ich freue mich auf Antworten, ob es sich um eine gut Grund ...

Ich möchte nicht, dass jemand die error_base Art werfen zu können, aber die Ausnahmetypen error_oh_shucks und error_oh_blast identische Funktionalität und ich will es nicht zweimal schreiben. Die Pimpl Komplexität ist notwendig auszusetzen std::string meine Kunden zu vermeiden, und die Verwendung von std::auto_ptr erfordert den Copykonstruktor.

Der öffentliche Header enthält die Ausnahme-Spezifikationen, die den Kunden zur Verfügung stehen wird verschiedene Arten von Ausnahmen zu unterscheiden von meiner Bibliothek geworfen:

// error.h

#include <exception>
#include <memory>

class exception_string;

class error_base : public std::exception {
 public:
  error_base(const char* error_message);
  error_base(const error_base& other);
  virtual ~error_base() = 0; // Not directly usable

  virtual const char* what() const;
 private:
  std::auto_ptr<exception_string> error_message_;
};

template<class error_type>
class error : public error_base {
 public:
   error(const char* error_message) : error_base(error_message) {}
   error(const error& other) : error_base(other) {}
   ~error() {}
};

// Neither should these classes be usable
class error_oh_shucks { virtual ~error_oh_shucks() = 0; }
class error_oh_blast { virtual ~error_oh_blast() = 0; }

Und hier ist die gemeinsame Umsetzung:

// error.cpp

#include "error.h"
#include "exception_string.h"

error_base::error_base(const char* error_message)
  : error_message_(new exception_string(error_message)) {}

error_base::error_base(const error_base& other)
  : error_message_(new exception_string(other.error_message_->get())) {}

error_base::~error_base() {}

const char* error_base::what() const {
  return error_message_->get();
}

Die exception_string Klasse, geheim gehalten, versteckt std :: string aus meiner öffentlichen Schnittstelle:

// exception_string.h

#include <string>

class exception_string {
 public:
  exception_string(const char* message) : message_(message) {}

  const char* get() const { return message_.c_str(); }
 private:
  std::string message_;
};

Mein Code dann wirft einen Fehler wie:

#include "error.h"

throw error<error_oh_shucks>("That didn't work");

Die Verwendung einer Vorlage für error ist ein wenig überflüssig. Es spart ein Stück Code auf Kosten Kunden zu verlangen, Fehler zu fangen:

// client.cpp

#include <error.h>

try {
} catch (const error<error_oh_shucks>&) {
} catch (const error<error_oh_blast>&) {
}

Vielleicht gibt es eine andere REAL USE-CASE von rein virtuellen Destruktor, die ich eigentlich nicht in anderen Antworten siehe:)

Zuerst bin ich völlig einverstanden mit deutlicher Antwort: Es ist, weil das Verbot rein virtuellen destructor eine zusätzliche Regel in Sprachspezifikation benötigen würde. Aber es ist noch nicht die Verwendung Fall, dass Mark fordern:)

Zuerst vorstellen, dass diese:

class Printable {
  virtual void print() const = 0;
  // virtual destructor should be here, but not to confuse with another problem
};

und so etwas wie:

class Printer {
  void queDocument(unique_ptr<Printable> doc);
  void printAll();
};

Einfach - Wir haben Schnittstelle Printable und einige „Container“ halten alles, was mit dieser Schnittstelle. Ich denke, hier ist es ganz klar ist, warum print() Methode virtuelle rein ist. Es könnte einige Körper hat, aber im Fall gibt es keine Standardimplementierung, rein virtuell ist eine ideale „Implementierung“ (= „muss von einer abgeleiteten Klasse zur Verfügung gestellt werden“).

Und nun vorstellen, genau gleich, außer es nicht für den Druck ist, sondern für die Vernichtung:

class Destroyable {
  virtual ~Destroyable() = 0;
};

Und auch ein ähnlicher Behälter sein könnte:

class PostponedDestructor {
  // Queues an object to be destroyed later.
  void queObjectForDestruction(unique_ptr<Destroyable> obj);
  // Destroys all already queued objects.
  void destroyAll();
};

Es ist Use-Case aus meiner realen Anwendung vereinfacht. Der einzige Unterschied besteht darin, dass „besondere“ Methode (destructor) anstelle von „normalen“ print() verwendet. Aber der Grund, warum es rein virtuell ist immer noch das gleiche - es gibt keinen Standard-Code für die Methode. Ein bisschen verwirrend könnte die Tatsache sein, dass es einige destructor sein muss effektiv und Compiler erzeugt tatsächlich einen leeren Code dafür. Aber aus der Sicht eines Programmierer reines Virtua noch heißt: „Ich habe keinen Standard-Code, muss es von abgeleiteten Klassen zur Verfügung gestellt werden“

Ich denke, es ist nicht jede große Idee hier, nur mehr Erklärung, dass reiner Virtua funktioniert wirklich einheitlich - auch für Destruktoren

.

Dies ist ein Jahrzehnt altes Thema :) Lesen Sie die letzten 5 Absätze von Artikel # 7 auf „Effective C ++“ Buch für Details, geht von „Gelegentlich kann es zweckmäßig sein, eine Klasse eine rein virtuelle destructor zu geben ....“

1) Wenn Sie die abgeleiteten Klassen erfordern wollen clean-up zu tun. Das ist selten.

2) Nein, aber Sie wollen es virtuell sein, aber.

müssen wir destructor virtuelle bacause der Tatsache machen, dass, wenn wir das Destruktor virtuell machen nicht dann Compiler wird nur der Inhalt der Basisklasse zerstören, n alle abgeleiteten Klassen bleiben un verändert, bacuse Compiler wird die destructor nicht nennen einer anderen Klasse mit Ausnahme der Basisklasse.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top