Constrcutor und cast operator
-
CreateWindow liefert ein rvalue. rvalues können nicht an non-const referenzen gebunden werden. um den gewünschten effekt zu rezielen, müsstest du eine hintertür analog zu auto_ptr verwenden. damit das nicht zu mehrdeutigkeiten führt müsstest du dann allerdings auf den operator type* verzichten (oder evtl. geht es auch, wenn der pointer-essende konstruktor explicit ist):
template <class type> class nuke_ptr { private: bool released; type *myPtr; struct Ref { Ref(nuke_ptr* p) : p(p) {} nuke_ptr* p; }; public: explicit nuke_ptr(type *ptr = NULL) : myPtr(ptr), released(ptr==NULL) // construct from object pointer { } nuke_ptr(nuke_ptr<type> &ref) : myPtr(ref.myPtr), released(ref.released) // construct from other nuke_ptr { ref.released = true; } nuke_ptr(Ref ref) : myPtr(ref.p->myPtr), released(ref.p->released) // construct from other nuke_ptr { ref.p->released = true; } operator Ref() // nicht const! { return Ref( this ); } operator type*() const // cast-to-address operator { return myPtr; } };PS: per const referenz und mutable geht natürlich auch. das ist aber ziemlich böse, denn der wert von released beeinflusst - so nehme ich jedenfalls an - beobachbares verhalten. mit dieser lösung würde man also völlig auf const-correctness verzichten.
-
mach mal so:
[cpp]template <class type> class nuke_ptr
{
private:
mutable bool released;
type *myPtr;public:
nuke_ptr(type *ptr = NULL) : myPtr(ptr), released(ptr==NULL) // construct from object pointer
{
}nuke_ptr(const nuke_ptr<type> &ref) : myPtr(ref.myPtr), released(ref.released) // construct from other nuke_ptr
{
ref.released = true;
}operator type*() const // cast-to-address operator
{
return myPtr;
}};[/cpp]
ich würde so was einfügen:
nuke_ptr()
: myPtr(0),
released(true)
{
}explicit nuke_ptr(type *pt)
: myPtr(ptr),
released(ptr==NULL) // construct from object pointer
{
}
-
Danke Euch beiden! Die Lösung funktioniert!
Daß temporäre Objekte const sind, war mir grade entfallen. Mist. Habe schon zu lange nicht mehr C++ programmiert.
Ich wäre aber wohl nicht auf diese Lösung gekommen, denn "mutable" habe ich eigentlich (auch wenn ich es kenne) noch nie verwendet. Ist das eine übliche Anwendung? Und auch interessant: Wird das auch von den halbwegs aktuellen Compiler (z.B GCC) unterstützt? Mir ist "mutable" bisher als ein Feature erschienen, daß man bei sauberer Programmierung kaum braucht (so ähnlich wie friend, dessen Anwendung mir auch nur vorteilhaft erscheint, wenn eine Klassen praktisch nur von *einer* festen Partnerklasse verwendet wird und darum direkt gekoppelt werden kann, weil Zugriffsfunktionen unnötig sind).
Was das explicit Statement anbelangt: Ist das hier wirklich noch nötig? Der cast wird jedenfalls verhindert, durch das const und immerhin nehme ich mir die Möglichkeit folgendes zu schreiben:
nuke_ptr<Window> x = new Window();und muss statt dessen die Form
nuke_ptr<Window> x(new Window());verwenden, die mir garnicht gefällt. Gibt es also einen Fall, in dem dieses explicit tatsächlich was bringt?
-
ich würde an deiner stelle auf den operator type* verzichten, das verursacht in der folge nur unnötige probleme (solche scherze wie vergleich, addition, subtraktion, subscripting sind plötzlich möglich, machen aber kaum das, was du beabsichtigst). wenn es ein smart pointer sein soll, lieber * und -> überladen und eine extra funktion get(). ausserdem sollte man hier const-overloads vorsehen; wenn der smartpointer const ist, soll auch get() ein const type* liefern.
und wie erwähnt, die lösung mit mutable ist in diesem zusammenhang böse, denn es geht auch ohne.
ach ja: temporäre objekte sind i.d.R. nicht const.
falls du dich gegen die mutable lösung entscheidest, muss operator= ebenfalls noch analog geschrieben werden
nuke_ptr& operator=(nuke_ptr& ref) // ... nuke_ptr& operator=(Ref ref) // ...
-
camper schrieb:
ich würde an deiner stelle auf den operator type* verzichten, das verursacht in der folge nur unnötige probleme (solche scherze wie vergleich, addition, subtraktion, subscripting sind plötzlich möglich, machen aber kaum das, was du beabsichtigst). wenn es ein smart pointer sein soll, lieber * und ->
überladen und eine extra funktion get().* und -> sind auch überladen. Und ja, das soll sowas wie ein smart pointer werden. Naja, oder im Augenblick mehr ein Experiment damit.
Allerdings mit der Absicht, daß ich einen solchen nuke_ptr genau so verwenden kann, wie einen Standard-Zeiger. Ausgenommen natürlich die Vernichtung des Objekts. Der nuke_ptr soll inbesondere, im Gegensatz zum auto_ptr, kopiert werden können und nach einem release() soll er weiterhin die Adresse enthalten. Was ich dem Benutzer dabei natürlich nicht abnehmen kann, ist das Verständnis, wie der nuke_ptr funktioniert.
Darum will ich auch garnicht verhindern, daß jemand mit der gecasteten Adresse Unfug anstellt. Das kann man mit einem herkömmlichen Zeiger auch. Ein get() hatte ich mal drin, aber ich fand es genau so ätzend wie das get() des auto_ptr. Ich möchte also nach Möglichkeit verhindern, auf get() angewiesen zu sein.
Allerdings sollte sowas, wie der implizite cast den ich beschrieben habe, natpürlich nicht passieren, denn das war schlicht falsch. Aber dafür habe ich ja nun eine Lösung.
Die Frage ist: wenn ich den Zeiger-Konstruktor nicht "explicit" deklariere, gibt es dann andere Fälle in denen das ganze schief geht?
camper schrieb:
ausserdem sollte man hier const-overloads vorsehen; wenn der smartpointer const ist, soll auch get() ein const type* liefern.
Das ist allerdings wahr!
camper schrieb:
und wie erwähnt, die lösung mit mutable ist in diesem zusammenhang böse, denn es geht auch ohne.
Wie?
Ich hänge mal den Code an, bin offen für jeden Kommentar.
template <class type> class nuke_ptr { private: mutable bool released; type *myPtr; //!< The wrapped object pointer public: nuke_ptr() : myPtr(NULL), released(true) // default constructor { } // explicit nuke_ptr(type *ptr) : myPtr(ptr), released(ptr==NULL) // construct from object pointer { } nuke_ptr(const nuke_ptr<type> &ref) : myPtr(ref.myPtr), released(ref.released) // construct from other nuke_ptr (nuke is transfered) { ref.released = true; } ~nuke_ptr() // destroy the object { if (!released) destroy(); } type* operator=(type* right) // copy operator (nuke is transfered) { // assert: this nuke_ptr isn't released. You first have to take care of the current object before assigning an new one! // - If you want to delete the pointed-to object, call destroy() instead of {delete x; x=NULL;}. // - If you want to move the pointed-to object from one pointer to any other, use release() instead of {y=x; x=NULL;}. // - If you want to re-use this nuke_ptr, please call destroy() or release() before assigning an new object. assert(released); myPtr = right; released = right!=NULL; return (myPtr); } type* operator=(const nuke_ptr<type> &right) // copy operator (nuke is transfered if right has a nuke) { myPtr = right.myPtr; released = right.released; right.released = true; return (myPtr); } type& operator*() // de-referencing operator { // assert: A NULL pointer may not be de-referenced assert(myPtr!=NULL); return (*myPtr); } const type& operator*() const // const de-referencing operator { // assert: A NULL pointer may not be de-referenced assert(myPtr!=NULL); return (*myPtr); } type* operator->() // de-referencing-and-access operator { // assert: A NULL pointer may not be de-referenced assert(myPtr!=NULL); return myPtr; } const type* operator->() const // const de-referencing-and-access operator { // assert: A NULL pointer may not be de-referenced assert(myPtr!=NULL); return myPtr; } operator type*() // cast-to-address operator { return myPtr; } operator const type*() const // const cast-to-address operator { return myPtr; } void destroy() // delete object and free pointer { delete myPtr; myPtr = NULL; released = true; } type* release() // disable nuke { released = true; return (myPtr); } }; template<class type> type* operator << (type *&left, nuke_ptr<type> &right) // Standard pointer copy operator that disables the nuke { left = right.release(); return (left); } template<class type> type* operator << (QPointer<type> &left, nuke_ptr<type> &right) // QPointer copy operator that disables the nuke { left = right.release(); return (left); }
-
hm, das geht so nicht:
nuke_ptr<int> a = new int; *a = 1; if ( /* ... */ ) { nuke_ptr<int> b = a; // b übernimmt ownership // ... } // b führt delete aus cout << *a; // ups, a zeigt auf freigegebenen speicherdas ist ja gerade der grund, warum auto_ptr den original zeiger löscht (und nicht bloss festlegt, wer den speicher löscht).
es ist unmöglich innerhalb des codes von nuke_ptr festzustellen, ob nun *this oder das argument länger leben wird. folglich hat man nur die möglichkeit, ownership zu teilen (zählen, wer zuletzt geht, macht das licht aus) oder zu übertragen.
es genügt nicht, des released flag zu übertragen, sobald released gesetzt ist, ist dein smart pointer nicht mehr sicher dereferenzierbar.
-
camper schrieb:
ach ja: temporäre objekte sind i.d.R. nicht const.
Sondern? Zumindest als Rückgabewerte sind sie das, nicht?
camper schrieb:
falls du dich gegen die mutable lösung entscheidest, muss operator= ebenfalls noch analog geschrieben werden
nuke_ptr& operator=(nuke_ptr& ref) // ... nuke_ptr& operator=(Ref ref) // ...Ich werde es mal probieren.
-
camper schrieb:
hm, das geht so nicht:
nuke_ptr<int> a = new int; *a = 1; if ( /* ... */ ) { nuke_ptr<int> b = a; // b übernimmt ownership // ... } // b führt delete aus cout << *a; // ups, a zeigt auf freigegebenen speicherdas ist ja gerade der grund, warum auto_ptr den original zeiger löscht (und nicht bloss festlegt, wer den speicher löscht).
es ist unmöglich innerhalb des codes von nuke_ptr festzustellen, ob nun *this oder das argument länger leben wird. folglich hat man nur die möglichkeit, ownership zu teilen (zählen, wer zuletzt geht, macht das licht aus) oder zu übertragen.
es genügt nicht, des released flag zu übertragen, sobald released gesetzt ist, ist dein smart pointer nicht mehr sicher dereferenzierbar.Dessen bin ich mir schon bewußt. Aber sieh es mal so: Ein normaler zeiger hat genau das selbe problem, wenn man delete ausführt. Dann muss man als Benutzer auch daran denken, den ersten zeiger zu löschen bzw. nicht mehr zu verwenden. Meine ansatz ist, nuke_ptr nur an nuke_ptr zuzuweisen für parameterübergaben oder Rückgabewerte. In obigem Fall würde ich einfach einen herkömmlichen Zeiger benutzen. Zumindest scheint mir das recht intuitiv.
auto_ptr hat versucht das zu ändern und ist damit nicht nur unintuitiv geworden, sondern hat zu allem Überfluss ein fast genau so schlimmes Problem erzeugt: zugriff auf auto_ptr, denen die Zuständigkeit bereits entzogen wurde.
Ich hatte auch Probleme mit Framwork-Funktionen, denen ich einen Zeiger auf ein Objekt übergebe, das dann vom Framework autoamtisch gelöscht wird. Wenn ich z.b. schreibe:
{auto_ptr<int> x = new int(5); uebernehmekontrolle(x.release()); machewas(x);}dann crasht es. Leider kann ich auch nicht schreiben
{auto_ptr<int> x = new int(5); uebernehmekontrolle(x.get()); machewas(x.get()); x.release();}weil bei einer exception in machewas() das objekt zwei mal gelöscht würde (einmal in uebernehmekontrolle() und einmal im auto_ptr() destruktor).
Das ist ein problem, das mir in diversen variationen begegnet. nuke_ptr kann das.
{nuke_ptr<int> x = new int(5); uebernehmekontrolle(x.release()); machewas(x);}Allerdings muss man sich natürlich immer gedanken machen wer das objekt löscht, da hast du recht.
Zählende zeiger gibts es ja auch, aber ich meine mich zu erinnern, daß die auch nicht der Weisheit letzter Schluss sind. Hatten die nicht auch große Nachteile, vom Verwaltungs-Aufwand mal ganz abgesehen? Können sie z.B. obiges Problem lösen?
Was ich auf jeden Fall vermeiden möchte, ist die Verwendnung eines halbend Dutzends verschiedener Smart-Pointer für verschiedene anwendungen. Ich brauche einen Smart-Pointer der alles kann, dafür nehme ich auch gerne den Aufwand auf mich, genau zu wissen, wie er funktioniert. Ich denke wenn ich versuche viele verschieden Smart-Pointer Typen zu verwenden, mache ich bei weitem mehr Fehler.
Ich würde aber auch gerne mehr Verwaltungsaufwand in Kauf nehmen. Wenn es also einen Smart-Pointer-Typ gibt (z.B. der zählende?) der alle Benutzungsfälle sauber abdeckt, wäre das natürlich fein.
-
Jordy schrieb:
Dann muss man als Benutzer auch daran denken, den ersten zeiger zu löschen bzw. nicht mehr zu verwenden. Meine ansatz ist, nuke_ptr nur an nuke_ptr zuzuweisen für parameterübergaben oder Rückgabewerte. In obigem Fall würde ich einfach einen herkömmlichen Zeiger benutzen. Zumindest scheint mir das recht intuitiv.
auto_ptr hat versucht das zu ändern und ist damit nicht nur unintuitiv geworden, sondern hat zu allem Überfluss ein fast genau so schlimmes Problem erzeugt: zugriff auf auto_ptr, denen die Zuständigkeit bereits entzogen wurde.
nö, das problem hast du nur mit deinem nuke_ptr, nicht mit auto_ptr:
- wenn ein auto_ptr kopiert wird, ist das original in jedem falle leer
- wenn ein nuke_ptr kopiert wird, ist das original vielleicht noch verwendbar. es gibt aber keinerlei möglichkeit festzustellen, ob ein nuke_ptr, der kopiert wurde, noch verwendbar ist (sofern man eben nur das original hat). das bedeutet keinerlei fortschritt gegenüber auto_ptr, im gegenteil. insbesondere ist dein nuke_ptr eben nicht zur parameterübergabe geeignet (ausser in fällen, in denen man auch auto_ptr verwenden könnte). als funktionsergebnis bietet nuke_ptr ebenfalls keinerlei vorteile gegenüber auto_ptr.Ich hatte auch Probleme mit Framwork-Funktionen, denen ich einen Zeiger auf ein Objekt übergebe, das dann vom Framework autoamtisch gelöscht wird. Wenn ich z.b. schreibe:
{auto_ptr<int> x = new int(5); uebernehmekontrolle(x.release()); machewas(x);}dann crasht es. Leider kann ich auch nicht schreiben
{auto_ptr<int> x = new int(5); uebernehmekontrolle(x.get()); machewas(x.get()); x.release();}weil bei einer exception in machewas() das objekt zwei mal gelöscht würde (einmal in uebernehmekontrolle() und einmal im auto_ptr() destruktor).
das problem hat aber nichts mit smartpointer an sich zu tun. du kannst ein objekt nunmal nur genau einmal löschen, kein smartpointer irgendwelcher art wird es dir erlauben, leichenfledderei zu betreiben

Das ist ein problem, das mir in diversen variationen begegnet. nuke_ptr kann das.
{nuke_ptr<int> x = new int(5); uebernehmekontrolle(x.release()); machewas(x);}wieso sollte das plötzlich gehen. *x wird immer noch in der funktion uebernahmekontrolle gelöscht; alles was danach passiert ist undefiniert. der grosse nachteil ist, dass man x in diesem falle nicht ansieht, dass es undefiniert ist. es könnte durchaus gelegentlich eine weile gut gehen, aber früher oder später hast du abstürze oder falsche ergebnisse an unerwarteten stellen. bei auto_ptr kannst du jederzeit feststellen, ob man noch etwas damit anfangen kann (sofern du nicht mittels get() kopierst, wie im zweiten fall, welcher in JEDEM falle zum doppelten löschen führt, und damit wenigstens reproduzierbar ist).
Allerdings muss man sich natürlich immer gedanken machen wer das objekt löscht, da hast du recht.
Zählende zeiger gibts es ja auch, aber ich meine mich zu erinnern, daß die auch nicht der Weisheit letzter Schluss sind. Hatten die nicht auch große Nachteile, vom Verwaltungs-Aufwand mal ganz abgesehen? Können sie z.B. obiges Problem lösen?
Was ich auf jeden Fall vermeiden möchte, ist die Verwendnung eines halbend Dutzends verschiedener Smart-Pointer für verschiedene anwendungen. Ich brauche einen Smart-Pointer der alles kann, dafür nehme ich auch gerne den Aufwand auf mich, genau zu wissen, wie er funktioniert. Ich denke wenn ich versuche viele verschieden Smart-Pointer Typen zu verwenden, mache ich bei weitem mehr Fehler.
Ich würde aber auch gerne mehr Verwaltungsaufwand in Kauf nehmen. Wenn es also einen Smart-Pointer-Typ gibt (z.B. der zählende?) der alle Benutzungsfälle sauber abdeckt, wäre das natürlich fein.
boost.smart_ptr ist wahrscheinlich das richtige für dich. das problem ist ja nicht der smart pointer an sich, sondern die zu grunde liegende frage, wer wann welches objekt besitzt. die antwort auf diese frage bestimmt dann die wahl des smart pointers, der diese form des besitzes modelliert.
-
camper schrieb:
nö, das problem hast du nur mit deinem nuke_ptr, nicht mit auto_ptr:
- wenn ein auto_ptr kopiert wird, ist das original in jedem falle leer
- wenn ein nuke_ptr kopiert wird, ist das original vielleicht noch verwendbar. es gibt aber keinerlei möglichkeit festzustellen, ob ein nuke_ptr, der kopiert wurde, noch verwendbar ist (sofern man eben nur das original hat). das bedeutet keinerlei fortschritt gegenüber auto_ptr, im gegenteil. insbesondere ist dein nuke_ptr eben nicht zur parameterübergabe geeignet (ausser in fällen, in denen man auch auto_ptr verwenden könnte). als funktionsergebnis bietet nuke_ptr ebenfalls keinerlei vorteile gegenüber auto_ptr.Naja, bei auto_ptr bekomme ich halt einen Zugriff auf NULL anstatt auf eine unbestimmte adresse. Was ist daran besser? Zugegeben, bei einem auto_ptr kann ich vor dem zugriff überprüfen, ob das objekt nach vorhanden ist, aber an dem crahs der applikation ändert das nichts, stimmts?
wieso sollte das plötzlich gehen. *x wird immer noch in der funktion uebernahmekontrolle gelöscht; alles was danach passiert ist undefiniert.
Autsch. Mist. Sorry, ich habe mich nicht richtig ausgedrückt. Die Funktion (eigentlich die methode) uebernehmekontrolle() löscht das objekt nicht sofort. Es hängt das objekt in eine Verwaltungshirarchie ein, in der es später automatisch gelöscht wird.
Schau dir die Codeschnipsel unter der prämisse nochmal an, dann gibt es Sinn

boost.smart_ptr ist wahrscheinlich das richtige für dich.
Schau ich mir mal an.
[code]das problem ist ja nicht der smart pointer an sich, sondern die zu grunde liegende frage, wer wann welches objekt besitzt. die antwort auf diese frage bestimmt dann die wahl des smart pointers, der diese form des besitzes modelliert.[/quote]
Stimmt. Solange man nicht managed code schreibt, ist das sicher richtig. Wenn ich mir aber sowieso ständig Gedanken machen muss, wann, wie und wo das jeweils referenzierte objekt gelöscht werden soll, dann kann ich auch gleich so ein primitives Ding wie nuke_ptr nehmen. Sicher, ich kann dabei Fehler machen. das kann ich aber auch, wenn ich viele verschiedene Smart-Pointer benutze... wenn ich den falschen Pointer wähle, schmiert meine Applikation genau so schnell ab, wie bei einer falschen Benutzung von nuke_ptr. Außerdem kann man, in Verbindung mit einem entsprechenden Framework, das eben selbst keine Smart-Pointer verwendet, mit jedem Smart-Pointer, egal wie komplex oder primitiv, seine Software ganz schnell verwanzen.
Mir scheint, mit Smart-pointern wird man niemals sicherheit erreichen.