Frage

Betrachten Sie dieses klassische Beispiel verwendet, um zu erklären, was nicht mit Vorwärtsdeklarationen zu tun:

//in Handle.h file
class Body;

class Handle
{
   public:
      Handle();
      ~Handle() {delete impl_;}
   //....
   private:
      Body *impl_;
};

//---------------------------------------
//in Handle.cpp file

#include "Handle.h"

class Body 
{
  //Non-trivial destructor here
    public:
       ~Body () {//Do a lot of things...}
};

Handle::Handle () : impl_(new Body) {}

//---------------------------------------
//in Handle_user.cpp client code:

#include "Handle.h"

//... in some function... 
{
    Handle handleObj;

    //Do smtg with handleObj...

    //handleObj now reaches end-of-life, and BUM: Undefined behaviour
} 

Ich verstehe von dem Standard, dass dieser Fall in Richtung UB geleitet wird, da Körper Destruktor nicht trivial ist. Was ich versuche ist wirklich die Ursache für das zu verstehen.

Ich meine, scheint das Problem durch die Tatsache, „ausgelöst“ wird, dass Handelte die dtor inline ist, so dass der Compiler tut so etwas wie die folgenden „Inline-Erweiterung“ (fast pseudo-Code hier).

inline Handle::~Handle()
{
     impl_->~Body();
     operator delete (impl_);
}

In allen Übersetzungseinheiten (nur in diesem Fall Handle_user.cpp), wo eine Handle Instanz zerstört wird, nicht wahr? Ich kann einfach nicht verstehen: ok, wenn die oben Inline-Erweiterung Erzeugen der Compiler keine vollständige Definition der Körper Klasse hat, aber warum kann es nicht einfach, den Linker Entschlossenheit für die impl_->~Body() Sache haben und so haben sie den Körper nennen destructor-Funktion, die in der Implementierung-Datei tatsächlich definiert wird?

Mit anderen Worten: Ich verstehe, dass an der Stelle der Griff Zerstörung ist der Compiler nicht einmal wissen, ob eine (nicht-triviale) destructor oder nicht für Körper vorhanden ist, aber warum kann es nicht tun, wie es immer der Fall ist, dass einen „Platzhalter“ für den Linker wird verlassen, zu füllen und schließlich einen Linker „nicht aufgelöste externe“ hat, wenn diese Funktion wirklich nicht verfügbar ist?

Am I fehlt hier etwas Großes (und in diesem Fall sorry für die dumme Frage)? Wenn das nicht der Fall ist, bin ich einfach nur neugierig auf die Logik dahinter zu verstehen.

War es hilfreich?

Lösung

Um mehrere Antworten zu kombinieren und meine eigenen hinzufügen, ohne eine Klassendefinition der anruf Code nicht kennt:

  • , ob die Klasse eine erklärte destructor, oder wenn der Standard-destructor verwendet werden soll, und wenn ja, ob der Standard destructor ist trivial,
  • , ob der destructor ist zugänglich für die Telefonvorwahl,
  • , welche Basisklassen existieren und Destruktoren,
  • , ob der Destruktor virtuell ist. Virtuelle Funktionsaufrufe in der Tat verwenden eine andere Aufrufkonvention von nicht-virtuelle. Der Compiler kann nicht nur „den Code emittieren ~ Körper zu nennen“, und den Linker an Arbeit die Details später verlassen,
  • (dies ist nur in dank GMan), ob delete für die Klasse überlastet ist.

Sie können keine Member-Funktion auf einen unvollständigen Typen rufen für einige oder alle dieser Gründe (plus eine andere, die nicht zu Destruktoren gilt - Sie würden die Parameter oder Rückgabetyp nicht kennen). Ein destructor ist nicht anders. Also ich nicht sicher, was ich meinen Sie, wenn Sie sagen, „warum kann es nicht tun, wie sie es immer tut?“.

Wie Sie bereits wissen, ist die Lösung, die den destructor von Handle in der TU zu definieren, die die Definition von Body hat, gleiche Stelle wie Sie jede andere Mitglied Funktion von Handle definieren, welche Funktionen oder Verwendungen Datenelemente von Body aufruft. Dann an dem Punkt, wo delete impl_; kompiliert wird, werden alle Informationen zur Verfügung, den Code für diesen Anruf zu emittieren.

Beachten Sie, dass der Standard tatsächlich sagt, 5.3.5 / 5:

  

, wenn das Objekt, das gelöscht hat   unvollständiger Klassentyp an der Stelle der   Löschen und die komplette Klasse hat eine   nicht-triviale destructor oder   Freigabe-Funktion ist das Verhalten   nicht definiert.

Ich nehme an, dies ist so, dass Sie einen unvollständigen POD-Typen löschen können, gleich wie Sie es in C. g free könnten ++ gibt Ihnen eine ziemlich ernste Warnung, wenn Sie es versuchen, though.

Andere Tipps

Es weiß nicht, ob der destructor öffentlich sein wird oder nicht.

eine virtuelle Methode oder eine nicht-virtuelle Methode aufrufen sind zwei völlig verschiedene Dinge.

Wenn Sie eine nicht-virtuelle Methode aufrufen, hat der Compiler Code zu generieren, bedeutet diese:

  • setzen alle Argumente auf den Stapel
  • die Funktion aufrufen und den Linker sagen, dass es sollte den Anruf
  • lösen

Da wir über den destructor sprechen, gibt es keine Argumente auf dem Stapel legen, so dass es aussieht können wir einfach den Anruf tun und den Linker sagen, den Anruf zu lösen. Kein Prototyp benötigt.

Um jedoch eine virtuelle Methode Aufruf ist völlig anders:

  • setzen alle Argumente auf den Stapel
  • erhalten die vptr der Instanz
  • erhält den n-te Eintrag aus der V-Tabelle
  • rufen Sie die Funktion, auf die dieser n-ten Einspeisepunkten

Das ist völlig anders, so hat der Compiler wirklich wissen, ob Sie eine virtuelle oder nicht-virtuelle Methode aufrufen.

Die zweite wichtige Sache ist, dass die Compiler Bedürfnisse, auf das wissen Position der virtuelle Methode ist in der Vtable gefunden. Dazu braucht es auch die vollständige Definition der Klasse haben.

Ohne die richtige Erklärung von Body der Code in Handle.h nicht weiß, ob destructor virtual oder sogar zugänglich (das heißt öffentlich) ist.

Ich kann nur raten, aber vielleicht hat es mit der Fähigkeit von pro-Klasse Zuordnung Betreibern zu tun.

Das heißt:

struct foo
{
    void* operator new(size_t);
    void operator delete(void*);
};

// in another header, like your example

struct foo;

struct bar
{
    bar();
    ~bar() { delete myFoo; }

    foo* myFoo;
};

// in translation unit

#include "bar.h"
#include "foo.h"

bar::bar() :
myFoo(new foo) // uses foo::operator new
{}

// but destructor uses global...!!

Und jetzt haben wir die Zuteilung Operatoren nicht übereinstimmen, und nicht definiertes Verhalten eingetragen. Der einzige Weg, um sicherzustellen, dass nicht passieren kann, ist zu sagen: „die Art komplett machen“. Ansonsten ist es unmöglich, zu gewährleisten.

Das ist wirklich nur ein Spezialfall des Aufrufs eine Methode (den destructor, indirekt). effektiv löschen impl_ nur Anrufe den destructor und dann der entsprechende Operator (global oder Klasse) löschen. Sie können jeden nicht nennen andere Funktion auf einem unvollständigen Typ, also warum soll den Aufruf den destructor löscht besondere Behandlung gegeben werden?

Der Teil Ich bin nicht sicher ist, was Komplikation des Standard bewirkt, dass es statt nur machen, nicht definierte es wie im Methodenaufruf verbieten.

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