auto_ptr
-
nachdem ich gerade eben über einen tödlichen bug in der auto_ptr implementation von vc8.0 gestossen bin:
std::auto_ptr<X> foo() { return 0; }das kompiliert (für beliebiges X und führt sofort zum crash, man kann allerdings auch einen beliebigen objekt pointer benutzen: effekt ist noch schlimmer, dann crasht es nähmlich nicht sofort).
habe ich mir zum spass auch mal die implementation in der gnu stdlibc++ angesehen - der funktioniert auch nicht richtig. nach über 7 jahren sind weder microsoft noch die gcc entwickler in der lage einen korrekten auto_ptr zu implementieren - denn keiner von beiden geht mit pointern auf objekte mit virtuellen basen korrekt um. also hab ich das ganze mal selbst versucht (dauer: 15minuten):
template<typename Target> class auto_ptr_ref { private: template<typename> friend class auto_ptr; explicit auto_ptr_ref(void** pptr, Target* ptr) throw() : ptr_( ptr ), pptr_( pptr ) {} Target* ptr_; // wir müssen die konvertierung bereits im operator auto_ptr_ref durchführen, // denn eine convertierung der form (A*)(void*)(B*)p kann fehlerhaft sein, falls // A eine virtuelle basis von B ist void** pptr_; // der standard sagt zwar, dass auto_ptr_ref *this des ursprungstyps hält; // das nützt aber nichts, denn da auto_ptr kein POD ist, könnte das speicherlayout // je nach typ unterschiedlich aussehen (ok, ist weit hergeholt...). // wir speichern hier deshalb direkt die adresse des internen pointers // das ganze wäre wesentlich einfacher, falls auto_ptr_ref bereits den pointer aus auto_ptr // extrahieren dürfte... }; template<typename Pointee> class auto_ptr { template<typename> friend class auto_ptr; public: // konstruktion aus rohem zeiger explicit auto_ptr(Pointee* ptr = 0) throw() : ptr_( ptr ) {} // konstruktion aus lvalue auto_ptr(auto_ptr& rhs) throw() : ptr_( rhs.get() ) { rhs.ptr_ = 0; } template<typename Source> auto_ptr(auto_ptr< Source >& rhs ) throw() : ptr_( static_cast< Pointee* >( rhs.get() ) ) { rhs.ptr_ = 0; } // konstruktion aus rvalue auto_ptr(auto_ptr_ref< Pointee > rhs) throw() : ptr_( rhs.ptr_ ) { *rhs.pptr_ = 0; } ~auto_ptr() throw() { delete get(); } // zuweisung aus lvalue auto_ptr& operator=(auto_ptr& rhs) throw() { if ( this != &rhs ) { delete get(); ptr_ = rhs.get(); rhs.ptr_ = 0; } return *this; } template<typename Source> auto_ptr& operator=(auto_ptr< Source >& rhs ) throw() { delete get(); ptr_ = static_cast< Pointee* >( rhs.get() ); rhs.ptr_ = 0; return *this; } // zuweisung aus rvalue auto_ptr& operator=(auto_ptr_ref< Pointee > rhs) throw() { if ( rhs.pptr_ != &ptr_ ) // kann das eigentlich fehlschlagen ? { delete get(); ptr_ = rhs.ptr_; *rhs.pptr_ = 0; } return *this; } // konvertierung nach auto_ptr_ref template<typename Target> operator auto_ptr_ref< Target >() throw() { return auto_ptr_ref< Target >( &ptr_, get() ); } // und der ganze rest... Pointee* get() const throw() { return static_cast< Pointee* >( ptr_ ); } Pointee* release() throw() { Pointee* ptr = get(); ptr_ = 0; return ptr; } void reset(Pointee* ptr = 0) { if ( get() != ptr ) delete get(); ptr_ = ptr; } Pointee& operator*() const throw() { return *get(); } Pointee* operator->() const throw() { return get(); } private: void* ptr_; // muss generisch sein, da auto_ptr_ref keine informationen über den source-typ hat };wer fehler findet, bitte posten. ansonsten ist das hier zur freien verwendung um defekte bibliotheken zu reparieren :p
-
Kommt vielleicht daher, das Smartpointer (leider) immer noch skeptisch und stiefmütterlich von den C++-Programmierern behandelt werden. Sieht man auch hier im Forum. Wie oft hab ich schon gelesen, das auto_ptr zu nichts zu gebrauchen ist. Und wenn selbst std::vector und std::string als unnützt und unperformant angesehen werden? Dann wundert es mich nicht, das es dann auch den gcc- und vc-Implementierern egal ist.
Aber trotzdem danke für deine Mühe. Mir ist dieser Bug ehrlich gesagt nicht aufgefallen (muß ich mal ausprobieren).
-
schreib eine mail an dinkumware.
-
Der C++ Standard sagt:
explicit auto_ptr(X* p =0) throw();somit darf foo nicht compilieren. Was compilieren dürfte wäre { return auto_ptr<X>(0);} was es zumindest unter gcc 4 auch tut und auch net segfaulted oder ähnliches.
-
Camper! So, habe mal deinen Beispielcode unter VC++ 7.1 ausprobiert. Ergebnis:
error C2664: 'std::auto_ptr<_Ty>::auto_ptr(std::auto_ptr<_Ty> &) throw()': Konvertierung des Parameters 1 von 'int' in 'std::auto_ptr<_Ty> &' nicht möglich
Hem, war wohl in 7.1 richtig und ist jetzt in 8.0 falsch?

-
PlasmaHH schrieb:
Der C++ Standard sagt:
explicit auto_ptr(X* p =0) throw();somit darf foo nicht compilieren.
Warum darf es das nicht? 0 ist doch ein X* oder? IMHO sollte das schon kompilieren, nur crashen sollte es nicht.
-
ich hab ja auch nicht gesagt, dass es compilieren darf, das ist nicht der fall. das problem bei vc8 ist, dass der konstruktor auto_ptr_ref einen generischen void* pointer nimmt und weder explicit nocht private ist. fügt man eines davon ein, ist dies problem gelöst (es ist bei mir aufgetaucht als ich code, der mit nakten pointern arbeitet, mit auto_ptr zwecks exceptionsicherheit aufgerüstet habe - gelegentlich übersieht man dabei solche return 0 - und erwartet nat. dass der compiler darauf hinweist). das problem mit den virtuellen basen löst es allerdings nicht.
kleiner fehler übrigens oben, es muss heissen:
template<typename Source> auto_ptr(auto_ptr< Source >& rhs ) throw() : ptr_( implicit_cast< Pointee*, Source* >( rhs.get() ) ) { rhs.ptr_ = 0; } template<typename Source> auto_ptr& operator=(auto_ptr< Source >& rhs ) throw() { delete get(); ptr_ = implicit_cast< Pointee*, Source* >( rhs.get() ); rhs.ptr_ = 0; return *this; }wobei ich mir Stroustrup's implicit_cast geborgt habe:
template<class T, class U> T implicit_cast(U u) { return u; }mit static_cast wären ja downcasts möglich.
-
Jester schrieb:
PlasmaHH schrieb:
Der C++ Standard sagt:
explicit auto_ptr(X* p =0) throw();somit darf foo nicht compilieren.
Warum darf es das nicht? 0 ist doch ein X* oder? IMHO sollte das schon kompilieren, nur crashen sollte es nicht.
0 ist ein int, was aber egal ist; explizit heißt keine implizite typumwandlung (und die typumwandlung würde von X* nach auto_ptr<X> stattfinden)
-
nanunu schrieb:
0 ist ein int, was aber egal ist
Stimmt, im Großen und Ganzen hast Du recht. Aber 0 ist nicht nur ein int. 0 ist genausogut (leider) ein Pointer.
-
Jester schrieb:
0 ist genausogut (leider) ein Pointer.
Ansichtssache würde ich sagen.

Wie dem auch sei, alle Klarheiten beseitigt warum es nicht kompilieren darf ?
-
Nicht ganz - 0 ist die Nullzeiger-Konstante, die implizit konvertierbar ist in einen Nullzeiger eines konkreten Typs :p
-
noch eine kleine fehlerberichtigung: der schutz auf selbstzuweisung darf auch in der template version nicht fehlen, denn der user könnte diese version explizit aufrufen; analoges gilt für den operator=, der ein auto_ptr_ref nimmt.
-
7H3 N4C3R schrieb:
Nicht ganz - 0 ist die Nullzeiger-Konstante, die implizit konvertierbar ist in einen Nullzeiger eines konkreten Typs :p
Richtig. Somit würde augenscheinlich der auto_ptr<X>( X* ) ctor passen. Dieser ist aber explizit markiert, und das zurückgeben von einem X*, wobei die Funktion einen auto_ptr<X> zurückgibt, würde implizit den auto_ptr<X>(X*) ctor aufrufen, was aber das explizit keyword verbietet. Ein
return auto_ptr<X>(0);jedoch ruft diesen explizit auf, was auch ok ist.
Ähnlich verhält es sich hier:X* x = ...; // beliebiger wert /* Produziert z.B. mit gcc richtigerweise einen fehler wie: error: conversion from ‘X*’ to non-scalar type ‘std::auto_ptr<X>’ requested */ auto_ptr<X> Xa = x; /* So wirds gemacht: */ auto_ptr<X> Xa(x); // oder auto_ptr<X> Xa = auto_ptr<X>(x);Was im übrigen auch sinn macht, denn wäre der ctor nicht explizit würde man oftmals mal gegen die Wand rennen, wenn so eine Konversion stattfindet und das Objekt einfach mal gelöscht wird.
-
Jester schrieb:
Stimmt, im Großen und Ganzen hast Du recht. Aber 0 ist nicht nur ein int. 0 ist genausogut (leider) ein Pointer.
Simmt, im Allgemeinen. Aber in diesem konkreten Fall muss 0 ein int sein, was zu einer Fehlermeldung à la "Konnte int nicht in auto_ptr<X> umwandeln" und sicher nicht zu einer Fehlermeldung "Konnte X* nicht in auto_ptr<X> umwandeln". Für die Konvertierung 0 -> X* gibt es hier kein Indiz.
-
nanunu schrieb:
Jester schrieb:
Stimmt, im Großen und Ganzen hast Du recht. Aber 0 ist nicht nur ein int. 0 ist genausogut (leider) ein Pointer.
Simmt, im Allgemeinen. Aber in diesem konkreten Fall muss 0 ein int sein, was zu einer Fehlermeldung à la "Konnte int nicht in auto_ptr<X> umwandeln" und sicher nicht zu einer Fehlermeldung "Konnte X* nicht in auto_ptr<X> umwandeln". Für die Konvertierung 0 -> X* gibt es hier kein Indiz.
Diese Konversion wird intern vom Compiler versucht, aber verworfen da sie nicht zu einem Ergebniss kommt. Daher wird der original Datentyp in der Fehlermeldung angegeben. Dies kann manchmal ähnlich verwirrend sein wie wenn man einen Pointer zu einem Typ hat, welcher von einer Basisklasse abgeleitet ist, und versucht den über einen solchen Basispointer weiterzugeben...