Ich habe einen Code in einem Header, der so aussieht:
#include <memory>
class Thing;
class MyClass
{
std::unique_ptr< Thing > my_thing;
};
Wenn ich diesen Header in eine Thing
CPP einbinde, die die Typdefinition nicht enthält , wird dies unter VS2010-SP1 nicht kompiliert:
1> C: \ Programme (x86) \ Microsoft Visual Studio 10.0 \ VC \ include \ memory (2067): Fehler C2027: Verwendung des undefinierten Typs 'Thing'
Ersetzen std::unique_ptr
durch std::shared_ptr
und es wird kompiliert.
Ich vermute also, dass es die aktuelle std::unique_ptr
Implementierung von VS2010 ist , die die vollständige Definition erfordert und vollständig implementierungsabhängig ist.
Oder ist es? Gibt es etwas in den Standardanforderungen, das es std::unique_ptr
der Implementierung unmöglich macht, nur mit einer Vorwärtsdeklaration zu arbeiten? Es fühlt sich seltsam an, da es nur einen Zeiger enthalten Thing
sollte, nicht wahr?
shared_ptr
/unique_ptr
". Die Tabelle am Ende sollte Ihre Frage beantworten.Antworten:
Von hier aus adoptiert .
Die meisten Vorlagen in der C ++ - Standardbibliothek erfordern, dass sie mit vollständigen Typen instanziiert werden. Allerdings
shared_ptr
undunique_ptr
sind teilweise Ausnahmen. Einige, aber nicht alle Mitglieder können mit unvollständigen Typen instanziiert werden. Die Motivation dafür ist, Redewendungen wie Pickel mit intelligenten Zeigern zu unterstützen, ohne undefiniertes Verhalten zu riskieren.Undefiniertes Verhalten kann auftreten, wenn Sie einen unvollständigen Typ haben und ihn aufrufen
delete
:Das obige ist ein gesetzlicher Code. Es wird kompiliert. Ihr Compiler gibt möglicherweise eine Warnung für den obigen Code wie den oben genannten aus. Wenn es ausgeführt wird, werden wahrscheinlich schlimme Dinge passieren. Wenn Sie sehr viel Glück haben, stürzt Ihr Programm ab. Ein wahrscheinlicheres Ergebnis ist jedoch, dass Ihr Programm stillschweigend Speicher verliert, da
~A()
es nicht aufgerufen wird.Die Verwendung
auto_ptr<A>
im obigen Beispiel hilft nicht. Sie erhalten immer noch das gleiche undefinierte Verhalten, als hätten Sie einen Rohzeiger verwendet.Trotzdem ist es sehr nützlich, an bestimmten Stellen unvollständige Klassen zu verwenden! Dies ist wo
shared_ptr
undunique_ptr
helfen. Wenn Sie einen dieser intelligenten Zeiger verwenden, können Sie mit einem unvollständigen Typ davonkommen, es sei denn, es ist ein vollständiger Typ erforderlich. Und am wichtigsten ist, dass Sie einen Fehler beim Kompilieren erhalten, wenn Sie versuchen, den Smart Pointer mit einem unvollständigen Typ zu diesem Zeitpunkt zu verwenden, wenn ein vollständiger Typ erforderlich ist.Kein undefiniertes Verhalten mehr:
Wenn Ihr Code kompiliert wird, haben Sie überall einen vollständigen Typ verwendet, den Sie benötigen.
shared_ptr
undunique_ptr
erfordern einen vollständigen Typ an verschiedenen Stellen. Die Gründe sind unklar und haben mit einem dynamischen Löscher gegenüber einem statischen Löscher zu tun. Die genauen Gründe sind nicht wichtig. Tatsächlich ist es in den meisten Codes nicht wirklich wichtig, dass Sie genau wissen, wo ein vollständiger Typ erforderlich ist. Nur Code, und wenn Sie es falsch verstehen, wird der Compiler es Ihnen sagen.Falls es für Sie jedoch hilfreich ist, finden Sie hier eine Tabelle, in der mehrere Mitglieder
shared_ptr
undunique_ptr
in Bezug auf Vollständigkeitsanforderungen dokumentiert sind. Wenn das Mitglied einen vollständigen Typ benötigt, hat der Eintrag ein "C", andernfalls wird der Tabelleneintrag mit "I" gefüllt.Alle Operationen, die Zeigerkonvertierungen erfordern, erfordern vollständige Typen für
unique_ptr
undshared_ptr
.Der
unique_ptr<A>{A*}
Konstruktor kannA
nur dann mit einer Unvollständigkeit davonkommen, wenn der Compiler keinen Aufruf an einrichten muss~unique_ptr<A>()
. Wenn Sie zum Beispiel dasunique_ptr
auf den Haufen legen , können Sie mit einem unvollständigen davonkommenA
. Weitere Details zu diesem Punkt finden Sie in der Antwort von BarryTheHatchet hier .quelle
unique_ptr
als Membervariable einer Klasse, nur explizit deklarieren eine destructor (und Konstruktor) in der Klassendeklaration (in der Header - Datei) und gehen Sie zu definieren , sie in der Quelldatei (und fügen Sie den Header mit der vollständigen Deklaration der Klasse, auf die verwiesen wird, in die Quelldatei ein), um zu verhindern, dass der Compiler den Konstruktor oder Destruktor automatisch in die Headerdatei einfügt (was den Fehler auslöst). stackoverflow.com/a/13414884/368896 erinnert mich auch daran.Der Compiler benötigt die Definition von Thing, um den Standarddestruktor für MyClass zu generieren. Wenn Sie den Destruktor explizit deklarieren und seine (leere) Implementierung in die CPP-Datei verschieben, sollte der Code kompiliert werden.
quelle
MyClass::~MyClass() = default;
In der Implementierungsdatei scheint es weniger wahrscheinlich zu sein, dass sie später versehentlich von jemandem entfernt wird, der annimmt, dass der Destruktorkörper gelöscht wurde, anstatt absichtlich leer gelassen zu werden.default
ed unddelete
d.MyClass::~MyClass() = default
es nicht in die Implementierungsdatei von Clang verschoben zu werden. (noch?)Dies ist nicht implementierungsabhängig. Der Grund dafür ist, dass
shared_ptr
der richtige Destruktor zur Laufzeit ermittelt wird - er ist nicht Teil der Typensignatur. Derunique_ptr
Destruktor ist jedoch Teil seines Typs und muss zur Kompilierungszeit bekannt sein.quelle
Es sieht so aus, als ob die aktuellen Antworten nicht genau festlegen, warum der Standardkonstruktor (oder Destruktor) ein Problem darstellt, leere Antworten, die in cpp deklariert sind, jedoch nicht.
Folgendes passiert:
Wenn die äußere Klasse (dh MyClass) keinen Konstruktor oder Destruktor hat, generiert der Compiler die Standardklassen. Das Problem dabei ist, dass der Compiler im Wesentlichen den standardmäßigen leeren Konstruktor / Destruktor in die .hpp-Datei einfügt. Dies bedeutet, dass der Code für den Standard-Konstruktor / Destruktor zusammen mit der Binärdatei der ausführbaren Host-Datei kompiliert wird, nicht zusammen mit den Binärdateien Ihrer Bibliothek. Diese Definitionen können jedoch die Teilklassen nicht wirklich konstruieren. Wenn der Linker in die Binärdatei Ihrer Bibliothek wechselt und versucht, den Konstruktor / Destruktor abzurufen, findet er keinen und Sie erhalten einen Fehler. Wenn sich der Konstruktor- / Destruktorcode in Ihrer CPP befand, steht in Ihrer Bibliotheksbinärdatei der Code zum Verknüpfen zur Verfügung.
Dies hat nichts mit der Verwendung von unique_ptr oder shared_ptr zu tun, und andere Antworten scheinen ein verwirrender Fehler in der alten VC ++ - Implementierung von unique_ptr zu sein (VC ++ 2015 funktioniert auf meinem Computer einwandfrei).
Die Moral der Geschichte ist also, dass Ihr Header frei von Konstruktor / Destruktor-Definitionen bleiben muss. Es kann nur ihre Erklärung enthalten. Zum Beispiel
~MyClass()=default;
funktioniert in hpp nicht. Wenn Sie dem Compiler erlauben, einen Standardkonstruktor oder -destruktor einzufügen, wird ein Linkerfehler angezeigt.Eine weitere Randnotiz: Wenn Sie diesen Fehler auch dann noch erhalten, wenn Sie Konstruktor und Destruktor in der CPP-Datei haben, liegt der Grund höchstwahrscheinlich darin, dass Ihre Bibliothek nicht ordnungsgemäß kompiliert wird. Zum Beispiel habe ich einmal einfach den Projekttyp in VC ++ von "Konsole" in "Bibliothek" geändert und diesen Fehler erhalten, weil VC ++ kein _LIB-Präprozessorsymbol hinzugefügt hat und genau dieselbe Fehlermeldung ausgegeben hat.
quelle
Nur der Vollständigkeit halber:
Header: Ah
Quelle A.cpp:
Die Definition von Klasse B muss von Konstruktor, Destruktor und allem gesehen werden, was B implizit löschen könnte. (Obwohl der Konstruktor in der obigen Liste nicht aufgeführt ist, benötigt in VS2017 sogar der Konstruktor die Definition von B. Und dies ist sinnvoll, wenn man dies berücksichtigt dass im Falle einer Ausnahme im Konstruktor das unique_ptr erneut zerstört wird.)
quelle
Die vollständige Definition der Sache ist zum Zeitpunkt der Instanziierung der Vorlage erforderlich. Dies ist der genaue Grund, warum das Pimpl-Idiom kompiliert wird.
Wenn es nicht möglich war, die Leute bitten, würde nicht Fragen wie diese .
quelle
Die einfache Antwort lautet stattdessen einfach shared_ptr.
quelle
Was mich betrifft,
Fügen Sie einfach den Header hinzu ...
quelle