webentwicklung-frage-antwort-db.com.de

Was kann in C++ einen rein virtuellen Funktionsaufruf verursachen?

Ich unterrichte eine C++ - Programmierklasse und habe genug Fehlerklassen gesehen, dass ich ein gutes Gefühl dafür habe, wie häufig auftretende C++ - Fehler zu diagnostizieren sind. Es gibt jedoch einen Hauptfehlertyp, für den meine Intuition nicht besonders gut ist: Welche Programmierfehler rufen reine virtuelle Funktionen auf? Der häufigste Fehler, den ich gesehen habe, ist das Aufrufen einer virtuellen Funktion von einem Basisklassenkonstruktor oder -destruktor. Gibt es noch andere, die ich beachten sollte, wenn ich beim Debuggen von Studentencode helfe?

33
templatetypedef

"Der häufigste Fehler, den ich je gesehen habe, der dazu führt, ist das Aufrufen einer virtuellen Funktion aus einem Konstruktor oder Destruktor der Basisklasse."

Wenn ein Objekt erstellt wird, zielt der Zeiger auf die virtuelle Dispatch-Tabelle anfangs auf die höchste Oberklasse und wird nur aktualisiert, wenn die Erstellung der Zwischenklassen abgeschlossen ist. Sie können also aus Versehen die reine virtuelle Implementierung bis zu dem Punkt aufrufen, an dem eine Unterklasse - mit ihrer eigenen Implementierung der überschreibenden Funktion - die Konstruktion abschließt. Dies kann die am meisten abgeleitete Unterklasse oder irgendwo dazwischen sein.

Dies kann passieren, wenn Sie einem Zeiger auf ein teilweise konstruiertes Objekt folgen (z. B. in einem Race-Zustand aufgrund von asynchronen oder Thread-Vorgängen).

Wenn ein Compiler Grund zu der Annahme hat, dass er den tatsächlichen Typ kennt, auf den ein Zeiger auf die Basisklasse verweist, kann er den virtuellen Dispatch vernünftigerweise umgehen. Sie könnten es verwechseln, indem Sie mit undefiniertem Verhalten etwas tun, wie eine Umbesetzung.

Während der Zerstörung sollte die virtuelle Dispatch-Tabelle zurückgesetzt werden, da abgeleitete Klassen zerstört werden, sodass die reine virtuelle Implementierung möglicherweise erneut aufgerufen wird.

Nach der Zerstörung kann die fortgesetzte Verwendung des Objekts über "herabhängende" Zeiger oder Referenzen die reine virtuelle Funktion aufrufen, aber in solchen Situationen gibt es kein definiertes Verhalten.

28
Tony Delroy

Hier sind einige Fälle, in denen ein reiner virtueller Anruf erfolgen kann.

  1. Bei Verwendung eines baumelnden Zeigers - ist der Zeiger kein gültiges Objekt. Die virtuelle Tabelle, auf die er zeigt, ist nur ein zufälliger Speicher, der NULL enthalten kann
  2. Falsche Umwandlung Wenn Sie einen static_cast mit dem falschen Typ (oder einer Umwandlung im C-Stil) verwenden, kann das Objekt, auf das Sie zeigen, nicht die richtigen Methoden in seiner virtuellen Tabelle haben (in diesem Fall ist es wirklich ist eine virtuelle Tabelle im Gegensatz zur vorherigen Option).
  3. DLL wurde entladen - Wenn das Objekt, an dem Sie sich halten, in einer gemeinsam genutzten Objektdatei (DLL, so, sl) erstellt wurde, die erneut entladen wurde, kann der Speicher auf Null gesetzt werden jetzt draußen
8
Motti

Dies kann beispielsweise der Fall sein, wenn die Referenz oder der Zeiger auf ein Objekt auf eine NULL-Position zeigt und Sie die Objektreferenz oder den Zeiger verwenden, um eine virtuelle Funktion in der Klasse aufzurufen. Zum Beispiel: 

std::vector <DerivedClass> objContainer;  
if (!objContainer.empty()) 
   const BaseClass& objRef = objContainer.front();  
// Do some processing using objRef and then you erase the first
// element of objContainer
objContainer.erase(objContainer.begin());   
const std::string& name = objRef.name();  
// -> (name() is a pure virtual function in base class, 
// which has been implemented in DerivedClass).

Zu diesem Zeitpunkt ist das in objContainer [0] gespeicherte Objekt nicht vorhanden. Wenn die virtuelle Tabelle indiziert ist, wird kein gültiger Speicherplatz gefunden. Daher wird ein Laufzeitfehler ausgegeben, der besagt "reine virtuelle Funktion". 

0
cppcoder