Pointer-to-impl und smart ptr
-
Howdy,
ich hab Probleme das pimpl idiom (genaugenommen ist es nur ein ähnliche Situation, es gibt mehrere Klassen, die über die selbe Impl implementiert werden und verschiedenes Verhalten haben) anzuwenden und dabei smart pointer zu verwenden. Folgende Klassendefinition:
class ManualGraphicalEntity { public: ManualGraphicalEntity(GameSceneManager&, const std::string& name, const std::string& model, float scale); void SetPosition(const Gameplay::Vector&); void SetDirection(const Gameplay::Vector&); void SetCastShadows(bool); void SetPlayerColor(float r, float g, float b); void SetMaterial(const std::string& materialName); private: GameSceneManager& mScene; const boost::shared_ptr<GraphicalEntity> mEntity; };Der shared_ptr ist natürlich unsinnig, weil die entity nicht geshared wird. Der std::auto_ptr benötigt jedoch die Typdefinition von GraphicalEntity, das ist bekannt und liest man oft. Im Internet finden sich auch haufenweise Hinweise, dass man das am besten mit dem boost::scoped_pointer umgeht, ich kann jedoch nicht bestätigen, dass dies funktioniert.
In boost findet sich folgender Quelltext, der das ausdrücklich verhindert:
template<class T> inline void checked_delete(T * x) { // intentionally complex - simplification causes regressions typedef char type_must_be_complete[ sizeof(T)? 1: -1 ]; (void) sizeof(type_must_be_complete); delete x; }Was ist denn jetzt der schlaueste Weg? Der shared_ptr wär schon akzeptabel, aber etwas unschön.
-
Bevor der Hinweis kommt, ich hab die Klasse schon noncopyable gemacht.

-
Eigentlich erfordert
boost::scoped_ptrkeinen vollständigen Typen bei der Deklaration:<a href= schrieb:
Boost Smart Pointers, Common Requirements">T may be an incomplete type at the point of smart pointer declaration.
Bei der Verwendung muss der Typ jedoch bekannt sein. Du wirst den
scoped_ptrirgendwo verwendet haben, ohne die Definition vonToffenzulegen. Wahrscheinlich geht es um die Zerstörung;checked_deletestellt einen kompletten Typ fürdeletesicher, damit dessen Destruktor aufgerufen werden kann.
-
Nexus schrieb:
Du wirst den
scoped_ptrirgendwo verwendet haben, ohne die Definition vonToffenzulegen. Wahrscheinlich geht es um die Zerstörung;Ja, natürlich. Die "Verwendung" ist der Destrkutor ~ManualGraphicalEntity() den der Compiler generiert. Ich hatte gehofft, der scoped_ptr braucht das nicht.
Was macht der jetzt genau besser als der auto_ptr? Anscheinend ja nichts...
-
Optimizer schrieb:
Was macht der jetzt genau besser als der auto_ptr? Anscheinend ja nichts...
Doch. Er verhindert, dass das Objekt nicht richtig freigegeben wird, indem er den Destruktoraufruf sicherstellt. Zudem ermöglicht er keine Move-Semantik, sondern verbietet Kopien ganz.
Wenn du Konstruktor und Destruktor deiner Klasse manuell implementierst und schaust, dass in deren Definitionen der Typ des
scoped_ptrs bekannt ist, sollte es eigentlich funktionieren.
-
Ok, die Frage war nicht präzise genug. Die sonstigen Unterschiede zwischen auto_ptr und scoped_ptr sind mir schon klar, aber anscheinend macht er nichts besser in Bezug auf die Behandlung von unvollständigen Typen im inline-Destruktor (außer nen Compilerfehler statt nur ner Warning).
Der shared_ptr zum Beispiel kann damit schon umgehen und ich hab vielleicht irrtümlicherweise angenommen, dass der scoped_ptr ähnlich schlau gemacht wurde...
Und das Folgende funktioniert auch mit dem std::auto_ptr:
class ManualGraphicalEntity : boost::noncopyable { public: ManualGraphicalEntity(GameSceneManager&, const std::string& uniqueName, const std::string& model, float scale); ~ManualGraphicalEntity(); // Bingo! Ist natürlich leer. void SetPosition(const Gameplay::Vector&); void SetDirection(const Gameplay::Vector&); void SetCastShadows(bool); void SetPlayerColor(float r, float g, float b); void SetMaterial(const std::string& materialName); private: GameSceneManager& mScene; const std::auto_ptr<GraphicalEntity> mEntity; };Wahrscheinlich bleib ich jetzt dabei. Obwohl ich den scoped_ptr konzeptionell ein bisschen schöner finde, ziehe ich dann doch den Standard-Typ vor.
-
Optimizer schrieb:
Die sonstigen Unterschiede zwischen auto_ptr und scoped_ptr sind mir schon klar, aber anscheinend macht er nichts besser in Bezug auf die Behandlung von unvollständigen Typen im inline-Destruktor (außer nen Compilerfehler statt nur ner Warning).
Das "ausser" finde ich recht wichtig, gerade weil Warnungen schnell mal vergessen gehen oder gar nicht erst erzeugt werden. Die Gefahr von Memory Leaks und undefiniertem Verhalten ist es mir schon wert, einen Fehler statt einer Warnung zu haben.

Optimizer schrieb:
Und das Folgende funktioniert auch mit dem std::auto_ptr:
Wenn du
scoped_ptrverwendest, geht das hier (sinngemäss) nicht?// MyClass.hpp #include <boost/scoped_ptr.hpp> class T; class MyClass { public: MyClass(); ~MyClass(); private: boost::scoped_ptr<T> Ptr; }; // MyClass.cpp #include "MyClass.hpp" #include "T.hpp" // Definition von T MyClass::MyClass() : Ptr(new T()) { } MyClass::~MyClass() // T ist im Destruktor vollständig bekannt { }
-
Der shared_ptr wär schon akzeptabel, aber etwas unschön.
Dann nimm shared_ptr.
Oder implementier dir selbst einen PimplPtr, der nicht kopierbar ist, aber trotzdem wie shared_ptr einen Deleter hat (zumindest intern).
Als Deleter musst du ja nur freie Funktionen unterstützen, die Freiheit die shared_ptr bietet brauchst du ja nicht. Dadurch fällt der ganze Overhead von dynamischer Speicheranforderung weg, und die Klasse wird auch viel einfacher.
Oder guck ob du eine fertige PimplPtr Klasse wo findest. Irgendwo hatte ich sowas mal gesehen - weiss aber nimmer wo. Vermutlich ein (abgelehnter?) Boost-Kandidat.
-
Wozu überhaupt ein Smartpointer an dieser Stelle, wenn doch sowieso Destruktor&co. implementiert werden muss?
-
camper schrieb:
Wozu überhaupt ein Smartpointer an dieser Stelle, wenn doch sowieso Destruktor&co. implementiert werden muss?
So wie ich Optimizer verstanden habe, wären die Big Three eigentlich nicht nötig (abgesehen von Pimpl hat die Klasse keine Speicherverwaltungsaufgaben). Kopieren und Zuweisen will er mit
noncopyablesowieso verhindern. Somit bliebe der Destruktor, in dem eigentlich eindeletestehen könnte, aber Smart-Pointers erleichtern z.B. auch im Konstruktor die Exceptionsicherheit.Kommt man eigentlich nicht um das leere Definieren des Destruktors bei bekanntem Typen, wenn man eine sichere Freigabe erzwingen will?
-
Kommt man eigentlich nicht um das leere Definieren des Destruktors bei bekanntem Typen, wenn man eine sichere Freigabe erzwingen will?
Fehlt hier ein "herum"?

Einen leeren dtor muss man nicht definieren, der Compiler-generierte macht genau dasselbe.
-
hustbaer schrieb:
Fehlt hier ein "herum"?

Tatsächlich, hehe. Ich sollte nach mehrmaligem Umformulieren vielleicht etwas genauer lesen...

hustbaer schrieb:
Einen leeren ctor muss man nicht definieren, der Compiler-generierte macht genau dasselbe.
Ich dachte, die compilergenerierten Konstruktoren und Destruktoren würden leeren Funktionen gleichkommen, die inline innerhalb der Klassendefinition definiert würden. Im Header ist der Typ des Smart-Pointers (bei meinem Beispiel
T) allerdings noch nicht vollständig bekannt, wodurchchecked_deletescheitert. In der Klassen-Implementierungsdatei (bei mirMyClass.cpp) hat man ein#include "T.hpp", das durch die Definition vonTermöglicht, im Destruktor der Klasse den Smart-Pointer richtig zu zerstören.
-
Hihi, und ich hab "ctor" statt "dtor" geschrieben, peinlich
(habs korrigiert)OK, ich habe deine Frage falsch verstanden. Genauer gesagt: das "bei bekanntem Typen".
Ja, in dem Fall macht es einen Unterschied. Netterweise verwendet da die Boost ja das checked_delete, was diesen Fehler von einem blöd zu findenden Runtime-Fehler in einen nicht zu übersehenden Compiletime-Fehler verwandelt

Aber wie schon erwähnt kann man sich ja selbst einen Smart-Pointer basteln, der korrekt zerstört, sich aber ansonsten wie scoped_ptr verhält (und abgesehen von einem zusätzlichen Funktionszeiger auch kaum Overhead hat).
-
wenn Ihr das kopieren beim auto_ptr verhindern wollt, macht das ding doch const !
beim Pimpl erzeugst die Impl klasse doch meist sowieso in der init Liste oder ???das mit dem Compilerfehler versus Warnung seh ich auch ned so tragisch wenn ich das new vergesse, gibts den crash(neben der Warnung) spaetestens zur laufzeit mit ner symantisch richtigen NULL referenz Meldung. Vergess ich das new in der initliste aber ned, krieg ich da ja scho die compilerfehler um die ohren geworfen, wenn meine klasse unvollstaendig definiert ist ...
Ciao ...
-
Die Definition des Destruktor der Öffentlichkeit (=dem Compiler) zu überlassen, läuft dem pimpl-Idiom zuwider, soll doch durch dieses erreicht werden, dass Implementationsdetails unsichtbar werden. Ein öffentlicher definierter Destruktor bewirkt das Gegenteil. Gleiches gilt im Grunde für Smartpointer für den pimpl, auch hier wird letztlich die Implementation eingeengt:
beispielsweise ist es denkbar, dass eine Implementationsklasse nicht mehr Speicher benötigt als ein einzelner Zeiger - in diesem Falle könnte man den Speicher, den der pimpl-Zeiger nach außen hin belegt, direkt wiederverwenden. In solchen Fällen wäre sogar denkbar, dass die vom Compiler generierten Copyctor/Zuweiungsoperator/Destruktor das richtig tun würden - ab dies bedeutet letztlich das Offenlegen von Implementationsdetails (konsequent zu Ende gedacht bedeutet das sogar, dass pimpl korrekterweise per void* implementiert werden sollte).
Steht einmal fest, diese Member sowieso implementiert werden müssen, ist der Nutzen eines Smartpointers praktisch null. Selbst das Argument der Exceptionsicherheit zieht bei einem einzelnen Member nicht. In Hinblick auf die Schwierigkeiten, die mit unvollständigen Typen und Smartpointern verbunden sind, sehe ich wirklich keinen Vorteil darin, diese auf Biegen und Brechen einsetzen zu wollen.
-
Ich glaub ich steig hier ned wirklich durch ...
Was ist denn nun das eigentliche Problem ???ab dies bedeutet letztlich das Offenlegen von Implementationsdetails
Es wird doch nix offen gelegt .... die einzige anforderung die zumindest der auto_ptr hat, ist das der Destruktor dem auto_ptr "zugaenglich" ist.
Man koennt auch den destruktor in der Impl Klasse privat machen, aber die friend deklaration aufn auto_ptr sieht sicher schrecklich aus, und erreicht auch nix wirklich, weil jeder ja nen auto_ptr anlegen koennte. Um die verwendung der klasse einzuschraenken, muesst man sich was anderes einfallen lassen. Aber das ist doch kein problem ?
Ne Impl klasse ist doch meist total versteckt in ner lib, ohne das man an deren header rankommt. wieso sollte ich die extra noch mal schuetzen ??? wieso sollte ich dann einen nicht public destruktor brauchen koennen ??? das leuchtet mir ned ein ...Die Definition des Destruktor der Öffentlichkeit (=dem Compiler) zu überlassen
versteh ich auch ned ... der auto_ptr funktioniert doch auch mit dem Compilergenerierten ???
Also bei mir laesst sich Code generieren, wo in der Impl klasse kein Destruktor definiert ist ! also das Ding in nen auto_ptr packen, in den header zur auto_ptr deklaration den Forward packen, und in der cpp, den vollstaendigen header inkludieren, laesst sich wunderbar compilieren.
Compiler ist VS 2005 ... ist das kein Standardverhalten ???Also ich muss den DTor weder expliziet definieren, noch muss ich die klasse kennen, nur die typen muessen bekannt sein (Forward). Ich seh das problem ned ...
Ciao ....
-
RHBaum schrieb:
Also ich muss den DTor weder expliziet definieren, noch muss ich die klasse kennen, nur die typen muessen bekannt sein (Forward). Ich seh das problem ned ...
Du weisst, was passiert - oder besser: nicht passiert - wenn delete auf einen Zeiger angewendet wird, dessen Pointeetyp unvollständig definiert ist?
-
Aah ich hab schon zu lang hier nicht mehr reingeschaut. Ich nehme noch kurz hierzu Stellung:
Nexus schrieb:
Wenn du
scoped_ptrverwendest, geht das hier (sinngemäss) nicht?// MyClass.hpp #include <boost/scoped_ptr.hpp> class T; class MyClass { public: MyClass(); ~MyClass(); private: boost::scoped_ptr<T> Ptr; }; // MyClass.cpp #include "MyClass.hpp" #include "T.hpp" // Definition von T MyClass::MyClass() : Ptr(new T()) { } MyClass::~MyClass() // T ist im Destruktor vollständig bekannt { }Doch, das geht genauso wie beim auto_ptr. Ich ziehe hier dann lediglich den std pointer vor, weil der scoped_ptr nichts besser macht, was für mich relevant wäre (die Warnung statt Fehler ist mir auch deutlich genug).
camper schrieb:
Steht einmal fest, diese Member sowieso implementiert werden müssen, ist der Nutzen eines Smartpointers praktisch null. Selbst das Argument der Exceptionsicherheit zieht bei einem einzelnen Member nicht. In Hinblick auf die Schwierigkeiten, die mit unvollständigen Typen und Smartpointern verbunden sind, sehe ich wirklich keinen Vorteil darin, diese auf Biegen und Brechen einsetzen zu wollen.
Da stimme ich nicht zu. Es ist eine Prinzipsache, für Member smart pointer zu benutzen und ich weiche nur in Ausnahmefällen davon ab. Es ist nicht auf "Biegen und Brechen", denn es ist kein Mehraufwand vorhanden gegenüber einem rohen Zeiger. Ich sehe auch keine großartigen Schwierigkeiten, es ist jetzt klar, dass wenn ich den Destruktor selber definiere (was ich bei einem rohen Zeiger auch müsste), alles funktioniert. Die Ersparnis durch den smart pointer ist zugegebenermaßen gering, aber wenn es sich nicht ändern lässt, kann ich dagegen auch nichts machen.
Das ganze Idiom ist halt ne Krücke, wenn es jemand grundsätzlich ablehnt, verstehe ich das sogar. Ansonsten gehört es IMHO schon zum guten Ton, ein automatisches Objekt zu verwenden, außer es bereitet wirklich Extra-Aufwand.
-
Vor einiger Zeit habe ich mir eine Smart-Pointer-Klasse implementiert, die ihr Pointee automatisch kopiert. Das scheint mir relativ geeignet für Pimpl zu sein (das Thema gabs auch mal im Forum).
Zumindest habe ich es mit einer Indirektion über Funktionszeiger geschafft, durch eine Pointee-Deklaration (keine Definition) im Header und ohne Implementierung der Big Three das gewünschte Verhalten zu erzielen. Ich bin mir nicht sicher, ob das auf jedem Compiler geht (ich benutze MSVC++). Für die Interessierten ist hier der wichtige Teil meiner Implementierung zu sehen, ich habe einiges rausgekürzt. Leider bin ich noch nicht gross zum Testen gekommen, in bisherigen Fällen wurde aber immer korrekt freigegeben. Ich wäre sehr froh um Kritik und Ratschläge! Ich bin danach auch gerne bereit, meine komplette Implementierung zu zeigen, aber der momentane Code ist schon recht gross.
Freie Funktionen zur Unterstützung:
// Stellt einen vollständig definierten Typen T sicher, damit dessen Destruktor // aufgerufen werden kann. Wendet delete auf den übergebenen Zeiger an. template <typename T> void checked_delete(const T* pointer) { sizeof(T); // T muss vollständig definiert sein delete pointer; } // Klasse, die ein Objekt klont, indem dessen Kopierkonstruktor aufgerufen wird. // Bei einem Nullzeiger als Argument wird ebenfalls ein Nullzeiger zurückgegeben. template <typename T> T* default_cloner(const T* origin) { return (origin == 0) ? 0 : new T(*origin); } // Klasse, die ein Objekt zerstört, indem delete angewandt wird. template <typename T> void default_deleter(T* pointer) { checked_delete(pointer); } // Tut nichts (wird für Funktionszeiger, deren Aufruf nichts bewirken soll und keinen // vollständigen Typen erfodert, benötigt). template <typename T> void empty_deleter(T*) { } // Tut nichts (wird für Funktionszeiger, deren Aufruf nichts bewirken soll und keinen // vollständigen Typen erfodert, benötigt). Gibt immer einen Nullzeiger zurück. template <typename T> T* empty_cloner(const T*) { return 0; }Klassendefinition mit einem Teil der Methoden:
// Klassentemplate owned_ptr: Smart-Pointer, der sein Objekt im Destruktor automatisch // freigibt und im Kopierkonstruktor und Zuweisungsoperator kopiert. // T ist der Typ, der verwaltet werden soll. template <typename T> class owned_ptr { public: typedef void (*deleter_type)(T*); typedef T* (*cloner_type) (const T*); public: // Standardkonstruktor: Setzt den Zeiger auf Null. owned_ptr(); // Konstruktor: Übernimmt Herrschaft über den übergebenen Zeiger und initialisiert // Deleter und Cloner. explicit owned_ptr(T* new_pointer, deleter_type deleter = default_deleter, cloner_type cloner = default_cloner); // Kopierkonstruktor: Kopiert die Instanz des übergebenen Smart-Pointers, falls // dieser nicht auf Null zeigt. Wirft bei Fehlschlag der Kopie eine Exception. owned_ptr(const owned_ptr& origin); // Destruktor: Gibt Speicher des Zeigers mittels Deleter frei. ~owned_ptr(); // Zuweisungsoperator: Kopiert die Instanz des übergebenen Smart-Pointers und gibt // den momentanen Inhalt von *this frei. owned_ptr& operator= (const owned_ptr& origin); // Tauscht Objekt-Zeiger, Deleter und Cloner zweier owned_ptr-Instanzen. void swap(owned_ptr& other); // Gibt den Speicher mittels Deleter frei und setzt den Zeiger auf Null zurück. void reset(); // Gibt den Speicher frei und setzt den neuen Zeiger. void reset(T* new_pointer, deleter_type deleter = default_deleter, cloner_type cloner = default_cloner); // Gibt Speicher frei und kopiert die Instanz des übergebenen owned_pts. // Wirft eine Exception, falls die Kopie fehlschlägt. void reset(const owned_ptr<T>& origin); private: // interner Zeiger auf Objekt T* m_ptr; // Zeiger auf Freigabefunktion deleter_type m_deleter; // Zeiger auf Klonfunktion cloner_type m_cloner; };Implementierung der Methoden (ja, ich mag die Trennung auch bei Templates, auch wenn ich nicht immer ganz konsequent bin):
#include <algorithm> template <typename T> owned_ptr<T>::owned_ptr() : m_ptr(0) , m_deleter(empty_deleter) , m_cloner(empty_cloner) { } template <typename T> owned_ptr<T>::owned_ptr(T* new_pointer, deleter_type deleter, cloner_type cloner) : m_ptr(new_pointer) , m_deleter(deleter) , m_cloner(cloner) { } template <typename T> owned_ptr<T>::owned_ptr(const owned_ptr<T>& origin) : m_ptr(origin.m_cloner(origin.m_ptr)) , m_deleter(origin.m_deleter) , m_cloner(origin.m_cloner) { } template <typename T> owned_ptr<T>::~owned_ptr() { (this->m_deleter)(m_ptr); } template <typename T> owned_ptr<T>& owned_ptr<T>::operator= (const owned_ptr<T>& origin) { reset(origin); return *this; } template <typename T> void owned_ptr<T>::swap(owned_ptr<T>& other) { std::swap(m_ptr, other.m_ptr); std::swap(m_deleter, other.m_deleter); std::swap(m_cloner, other.m_cloner); } template <typename T> void owned_ptr<T>::reset() { owned_ptr tmp; swap(tmp); } template <typename T> void owned_ptr<T>::reset(T* new_pointer, deleter_type deleter, cloner_type cloner) { owned_ptr tmp(new_pointer, deleter, cloner); swap(tmp); } template <typename T> void owned_ptr<T>::reset(const owned_ptr<T>& origin) { owned_ptr tmp(origin); swap(tmp); }
-
Optimizer schrieb:
Doch, das geht genauso wie beim auto_ptr. Ich ziehe hier dann lediglich den std pointer vor, weil der scoped_ptr nichts besser macht, was für mich relevant wäre (die Warnung statt Fehler ist mir auch deutlich genug).
Wobei weiterhin zu beachten ist, dass die Verwendung von auto_ptr zu undefiniertem Verhalten führt. Was jeder persönlich macht, ist sein Eigene Sache, aber in diesemForum weise ich nochmal darauf hin.
Optimizer schrieb:
Da stimme ich nicht zu. Es ist eine Prinzipsache, für Member smart pointer zu benutzen und ich weiche nur in Ausnahmefällen davon ab. Es ist nicht auf "Biegen und Brechen", denn es ist kein Mehraufwand vorhanden gegenüber einem rohen Zeiger. Ich sehe auch keine großartigen Schwierigkeiten, es ist jetzt klar, dass wenn ich den Destruktor selber definiere (was ich bei einem rohen Zeiger auch müsste), alles funktioniert. Die Ersparnis durch den smart pointer ist zugegebenermaßen gering, aber wenn es sich nicht ändern lässt, kann ich dagegen auch nichts machen.
Auf "Prinzipien" reagiere ich grundsätzlich allergisch, weil das inhaltsleer ist. Letztlich liegt die Begründung dafür, wie der Code geschrieben wird dann nicht mehr in diesem Code selbst, sondern in der Person, die ihn geschrieben hat. Kein Idiom oder Konstrukt ist Selbstzweck.
Es ist jetzt klar, dass Destruktor (und Copyctor/Copy-Zuweisung, falls benötigt) selbst implementiert werden müssen trotz der Verwendung von Smartpointern. Das ist in meinen Augen eine extrem negative Bilanz und nicht etwa ein "geringer Vorteil", weil es unnötig Aufmerksamkeit erfordert. An einem schlechten Tag hat man all diese Zusammenhänge auch schon mal wieder vergessen, schaut sich den Destruktor an, der scheinbar leer ist, und ersetzt ihn wieder durch den vom Compiler generierten (möglich jedenfalls bei auto_ptr oder smart_ptr ohne zu Compilerfehlern zu führen). Bei einem rohen Zeiger steht dort ein delete und es ist ganz klar, dass der Destruktor stehen bleiben muss.Optimizer schrieb:
Das ganze Idiom ist halt ne Krücke, wenn es jemand grundsätzlich ablehnt, verstehe ich das sogar. Ansonsten gehört es IMHO schon zum guten Ton, ein automatisches Objekt zu verwenden, außer es bereitet wirklich Extra-Aufwand.
Automatische Objekte treten hier gar nicht auf... in jedem Fall ist die Automatik aber eine Illusion - und Code, der lügt, ist in jedem Fall abzulehnen. Soll der Smartpointer einem doch die Aufgabe abnehmen, über die Zerstörung zum richtigen Zeitpunkt nachdenken zu müssen, geht das in diesem Fall ja offensichtlich völlig fehl.
// (1) class Impl; class MyClass { public: ... MyClass(const MyClass&); MyClass& operator=(const MyClass&); ~MyClass(); private: scoped_ptr<Impl> pimpl; }; // class Impl { .... }; MyClass::MyClass(const MyClass& other) : pimpl(new Impl(*other.pimpl)) { } MyClass& MyClass::operator=(const MyClass& rhs); { *pimpl = *rhs.pimpl; return *this; } MyClass::~MyClass() { } ////////////////////////////////////////////////// // (2) class Impl*; class MyClass { public: ... MyClass(const MyClass&); MyClass& operator=(const MyClass&); ~MyClass(); private: Impl* pimpl; }; // class Impl { .... }; MyClass::MyClass(const MyClass& other) : pimpl(new Impl(*other.pimpl)) { } MyClass& MyClass::operator=(const MyClass& rhs); { *pimpl = *rhs.pimpl; return *this; } MyClass::~MyClass() { delete pimpl; } ////////////////////////////////////////////////// // (3) noch etwas interessanter und gleich auf const-Korrektheit geachtet /// class MyClass { public: ... MyClass(const MyClass&); MyClass& operator=(const MyClass&); ~MyClass(); private: void* pimpl; }; ////// namespace { class Impl { .... }; static_assert<sizeof(Impl)<=sizeof(void*)>; Impl* impl(void* pimpl, MyClass*) { &return reinterpret_cast<Impl&>(pimpl); } const Impl* impl(void* pimpl, const MyClass*) { &return reinterpret_cast<const Impl&>(pimpl); } #define PIMPL impl( pimpl, this ) } MyClass::MyClass(const MyClass& other) { new (PIMPL) Impl( *impl( other.pimpl, &other ) ); } MyClass& MyClass::operator=(const MyClass& rhs); { *PIMPL = *impl( rhs.pimpl, &rhs ); return *this; } MyClass::~MyClass() { PIMPL->~Impl(); }Ich sehe wirklich nicht, wie Variante 1 einfacher als Variante 2 ist.
Nexus Vorschlag schaue ich mir noch in Ruhe an.
-
Ich verstehs immer noch ned !

wenn delete auf einen Zeiger angewendet wird, dessen Pointeetyp unvollständig definiert ist?
unvollstaendig definiert ? Der Code der den destruktor des auto_ptr baut, wird doch an der stelle generiert, wo die vollstaendige definition der klasse verfuegbar ist, bzw sein muss ???
Das heisst doch, der destruktor code vom autoptr wird durch die cpp der Proxy Klasse generiert. Dort muss der Dtor der IMPL Klasse eben doch vollstaendig definiert sein, also auch der richtige DTor ???
Waer an der Impl Klasse irgendwas unvollstaendig ... wuerde er den autoptr doch gar ned bauen koennen ???Hasst du mal nen beispiel fuer ?
Ciao ...