Reiner virtueller C++-Destruktor

Abdul Mateen 12 Oktober 2023
  1. Rufen Sie die abgeleitete Klassenfunktion unter Verwendung des Basisklassenzeigers auf
  2. Reine virtuelle Funktion und abstrakte Klasse
  3. Virtueller und rein virtueller Destruktor
  4. Rein virtueller Destruktor
Reiner virtueller C++-Destruktor

In diesem Tutorial werden wir die reinen virtuellen Destruktoren in C++ besprechen. Um das Konzept jedoch vollständig zu verstehen, müssen wir verschiedene verwandte Konzepte verstehen.

Zuerst sehen wir uns die Methode zum Aufrufen einer Funktion einer abgeleiteten Klasse mit einem Basisklassenzeiger an, und dann diskutieren wir Probleme des Destruktor-Aufrufs bei der Vererbung. Abschließend werden wir den reinen virtuellen Destruktor und seine Implementierungsprobleme diskutieren.

Rufen Sie die abgeleitete Klassenfunktion unter Verwendung des Basisklassenzeigers auf

Ein Basisklassenzeiger hat die Macht, auf jedes seiner abgeleiteten Klassenobjekte zu zeigen. Dies erspart uns das Deklarieren und Verwalten mehrerer abgeleiteter Klassenzeiger zum Handhaben verschiedener abgeleiteter Klassenobjekte.

Allerdings kann ein Basisklassenzeiger die abgeleiteten Klassenmethoden im Allgemeinen nicht aufrufen. Sehen Sie sich beispielsweise den folgenden Code an:

#include <iostream>
using namespace std;
class P {};
class C : public P {
 public:
  void f() { cout << "Function C\n"; }
};
int main() {
  P *ptr = new C;
  ptr->f();
  return 0;
}

Bei der Kompilierung zeigt dieser Code den folgenden Fehler:

virtual_destructor0.cpp: In function ‘int main()’:
virtual_destructor0.cpp:12:7: error: ‘class P’ has no member named ‘f’
  ptr->f();

Wenn wir dieselbe Funktion in der Basisklasse deklarieren, ruft der Compiler die Basisklassenfunktion anstelle der abgeleiteten Klassenfunktion auf. Siehe nächsten Code, wo wir die Funktion f in der Klasse P hinzugefügt haben:

#include <iostream>
using namespace std;
class P {
 public:
  void f() { cout << "Function P\n"; }
};
class C : public P {
 public:
  void f() { cout << "Function C\n"; }
};
int main() {
  P *ptr = new C;
  ptr->f();
  return 0;
}

Nach dem Hinzufügen der Funktion f sehen Sie die Ausgabe:

Function P

Das bedeutet, dass der Zeiger der Basisklasse die Funktion der Basisklasse aufruft, nicht die Funktion der abgeleiteten Klasse. Wir leiten die Regel ab, dass ein Klassenzeiger eine Funktion aus seiner Klasse aufrufen kann, nicht aus der Klasse des enthaltenden Objekts.

Es gibt jedoch noch ein weiteres entscheidendes Konzept namens Virtual Function. Die virtuelle Funktion ermöglicht es abgeleiteten Klassen, die Basisklassenfunktion zu überschreiben, die der Basisklassenzeiger aufrufen kann.

Funktionsüberschreibung bedeutet, dass die abgeleitete Klasse ihre eigene Funktionalität für die entsprechende Basisklassenfunktion bereitstellt. Die Basisklasse schreibt das Schlüsselwort virtual mit ihren Funktionen, um ein Überschreiben in der abgeleiteten Klasse zu ermöglichen.

Sehen Sie sich noch einmal den Code an:

#include <iostream>
using namespace std;
class P {
 public:
  virtual void f() { cout << "Function P\n"; }
};
class C : public P {
 public:
  void f() { cout << "Function C\n"; }
};
int main() {
  P *ptr = new C;
  ptr->f();
  return 0;
}

Beachten Sie das Hinzufügen des Schlüsselworts virtual vor der Basisklassenfunktion f(). Sehen Sie sich nach der neuen Hinzufügung die Ausgabe an:

Function C

Das bedeutet, dass der Basisklassenzeiger jetzt eine Funktion entsprechend dem Objekttyp aufruft, auf den dieser Basiszeiger verweist (d. h. das Mitglied der abgeleiteten Klasse wird ausgeführt). Denken Sie daran, dass das frühere Verhalten darin bestand, Mitglieder des Zeigertyps (d. h. der Basisklasse) aufzurufen.

Es ist jedoch die Wahl des Implementierers der abgeleiteten Klasse, der virtuellen Klasse Funktionalität zu geben, was bedeutet, dass die abgeleitete Klasse die virtuelle Basisfunktion überschreiben kann oder nicht.

Manchmal erzwingt der Architekt (Designer) von Klassen eine abgeleitete Klasse, um Basisklassenfunktionen zu implementieren, was mehr Konzepte wie abstrakte Basisklasse (auch ABC genannt) und reine virtuelle Funktion beinhaltet.

Wir werden sie schnell besprechen. Sie können die angegebenen Websites bei Bedarf besuchen.

Reine virtuelle Funktion und abstrakte Klasse

Die Basisklassen können eine rein virtuelle Funktion deklarieren, um die Implementierung virtueller Basismethoden in abgeleiteten Klassen zu erzwingen. Eine rein virtuelle Funktion hat keine Implementierung.

Darüber hinaus wird eine Klasse mit einer oder mehreren rein virtuellen Funktionen zu einer abstrakten Klasse. Abstrakte Klassen sind nicht instanziierbar (jedoch können wir ihre Zeiger deklarieren, die auf abgeleitete Klassenobjekte zeigen).

Die Syntax einer rein virtuellen Funktion ist etwas anders:

virtual void f() = 0;

Die rein virtuelle Funktion hat keine Definition oder Körper; daher ist es nicht abrufbar. Es erzwingt jedoch abgeleitete Klassen, um rein virtuelle Funktionen zu implementieren.

Andernfalls wird die abgeleitete Klasse abstrakt (d. h. nicht instanziierbar).

Um eine Klasse abstrakt zu machen, müssen wir umgekehrt nur eine abstrakte Funktion erstellen. Wir werden uns später in diesem Artikel auf diesen Satz beziehen.

Virtueller und rein virtueller Destruktor

Beachten Sie zunächst, dass der Destruktor nicht überschrieben werden kann. Um jedoch sowohl Basis- als auch untergeordnete Klassendestruktoren aufzurufen, sollte der Destruktor in der (Basis-)Klasse des Zeigers virtuell sein.

Andernfalls wird nur der untergeordnete Klassendestruktor aufgerufen. Schauen wir uns zum Beispiel den folgenden Code an:

#include <iostream>
using namespace std;
class P {
 public:
  ~P() { cout << "P Class Destructor\n"; }
};
class C : public P {
 public:
  ~C() { cout << "C Class Destructor\n"; }
};
int main() {
  P *ptr = new C;
  delete ptr;
  return 0;
}

Ausgang:

P Class Destructor

Hier sehen Sie, dass der Zeiger von der Klasse P und das Objekt von der Klasse C ist. Die Operation delete ruft nur den Destruktor der Basisklasse auf, was bedeutet, dass wir die Ressourcen der abgeleiteten Klasse nicht löschen müssen.

Dazu müssen wir in der Basisklasse einen virtuellen Destruktor deklarieren. Wie Sie bereits wissen, müssen wir dazu ein virtuelles Schlüsselwort mit dem Basisklassen-Destruktor hinzufügen.

Hier ist der Beispielcode:

#include <iostream>
using namespace std;
class P {
 public:
  virtual ~P() { cout << "P Class Destructor\n"; }
};
class C : public P {
 public:
  ~C() { cout << "C Class Destructor\n"; }
};
int main() {
  P *ptr = new C;
  delete ptr;
  return 0;
}

Ausgang:

C Class Destructor
P Class Destructor

Rein virtueller Destruktor

Kommen wir schließlich zum Hauptthema und lösen wir das Problem des Aufrufs von Destruktoren sowohl der übergeordneten als auch der abgeleiteten Klasse durch einen rein virtuellen Destruktor.

Wie bereits erwähnt, unterscheidet sich ein Destruktor von anderen Funktionen, und es gibt kein Konzept zum Überschreiben des Destruktors. Daher kann ein rein virtueller Destruktor eine abgeleitete Klasse nicht dazu zwingen, einen Destruktor zu implementieren.

Betrachten wir die Dinge nun aus einem anderen Blickwinkel. Um eine Klasse abstrakt zu machen, müssen wir mindestens eine rein virtuelle Funktion deklarieren.

In einem solchen Fall müssen die abgeleiteten Klassen die rein virtuelle Funktion implementieren, um eine konkrete Klasse zu werden. Also machen wir eine Klasse abstrakt, indem wir in dieser Klasse einen reinen virtuellen Destruktor deklarieren, der die abgeleitete Klasse nicht dazu zwingt, irgendetwas zu implementieren.

Chancen eines reinen virtuellen Destruktors

Denken Sie immer an das No Free Lunch-Theorem; Das Deklarieren eines reinen virtuellen Destruktors hat einige Komplikationen.

Sehen wir uns diese anhand des folgenden Beispiels an:

class P {
 public:
  virtual ~P() = 0;
};

Wenn wir diesen Code kompilieren, tritt der folgende Fehler auf:

/tmp/ccZfsvAh.o: In function `C::~C()':
virtual_destructor4.cpp:(.text._ZN1CD2Ev[_ZN1CD5Ev]+0x22): undefined reference to `P::~P()'
collect2: error: ld returned 1 exit status

Glücklicherweise gibt es für die meisten komplexen Probleme auf der Welt einfache Lösungen. Dieser Fehler tritt auf, weil der Compiler nach der Definition des Destruktors sucht, die nicht verfügbar ist.

Daher besteht die Lösung darin, den reinen virtuellen Destruktor zu definieren. Scheint ein Widerspruch zu sein; Compiler, der eine Definition einer rein virtuellen Funktion verlangt?

Ja, dies gehört zu einigen der anderen Vorteile von C++. Wir können keinen rein virtuellen Destruktor innerhalb der Klasse implementieren; Wir können es jedoch außerhalb der Klasse implementieren.

Sehen Sie sich den folgenden Code an:

class P {
 public:
  virtual ~P() = 0;
};
P::~P() { cout << "P Class Destructor\n"; }

In der letzten Zeile haben wir einen reinen virtuellen Destruktor außerhalb der Klasse definiert, und es wird keinen Compilerfehler geben. Außerdem können wir sowohl den Destruktor der Basisklasse als auch den Destruktor der abgeleiteten Klasse aufrufen:

C Class Destructor
P Class Destructor

Um eine abstrakte Basisklasse zu erstellen, können wir einen reinen virtuellen Destruktor darin deklarieren; Wir müssen diesen reinen virtuellen Destruktor jedoch außerhalb der Klasse definieren (wo wir jede Ressource/jeden Speicher bereinigen können). Auf diese Weise können wir sowohl den Destruktor der Basisklasse als auch den Destruktor der abgeleiteten Klasse aufrufen.

Verwandter Artikel - C++ Destructor