auto_ptr und polymorphismus



  • hallo,

    class eins { ... };
    class zwei : public eins { ... };
    
    void test(const std::auto_ptr<eins>& f) { ... }
    
    std::auto_ptr<eins> eins_eins(new eins());
    std::auto_ptr<eins> eins_zwei(new zwei());
    std::auto_ptr<zwei> zwei_zwei(new zwei());
    
    test(eins_eins);  // ok
    test(eins_zwei);  // ok 
    test(zwei_zwei);  // error C2664: 'test': Konvertierung des Parameters 1 von 'std::auto_ptr<_Ty>' in 'std::auto_ptr<_Ty> &' nicht möglich
    

    warum geht das nicht obwohl doch zwei von eins abstammt?
    könnte man das mit einem konvertierungsoperator hinkriegen (vorausgesetzt, man könnte auto_ptr beliebig ändern)?



  • auto_ptr<eins> und auto_ptr<zwei> sind zwei völlig unabhängige Typen, selbst wenn ihre Template-Parameter miteinander verwandt sind. Durchaus möglich, daß da eine Konvertierung helfen könnte - allerdings müsstest du dafür tief in die Trickkiste greifen, um die Besitz-Semantik von auto_ptr bei dieser Konvertierung nicht zu zerstören.



  • Übergib doch einfach eine const eins&. Die Übergabe einer const Referenz auf einen auto_ptr ist semantisch eh ziemlich fragwürdig. (Bin mir jetzt gerade nicht 100% sicher, ob dann auch die Konvertierung des auto_ptr<zwei> klappt)



  • vorweg: es geht nicht konkret um auto_ptr, es lässt sich damit nur einfacher darstellen. die besitzer-semantik kann man als korrekt umgesetzt annehmen.

    template <typename T>
    class auto_ptr {
      public :
         ...
        template <typename D> auto_ptr(const auto_ptr<D>& copy) { ptr=copy.ptr; }
        template <typename D> operator auto_ptr<D>() { return auto_ptr<D>(*this); }
    
      private :
        T* ptr;
    };
    

    gleicher fehler. kann das so überhaupt gehen?



  • Ja, so könnte man das eventuell ansetzen, aber es reicht eins von beidem - entweder Umwandlungs-Ctor oder -Operator. Wenn du beide anlegst, wird die Umwandlung mehrdeutig und damit nicht mehr möglich (weil der Compiler sonst nicht weiß, ob er smart_ptr<zwei>::operator smart_ptr<eins>() oder smart_ptr<eins>::smart_ptr(const smart_ptr<zwei>) verwenden soll)

    (PS: Mit auto_ptr verbindet man fast instinktiv die gleichnamige Standard-Klasse. Wenn du von einer eigenen Pointer-artigen Klasse redest, trifft es der Begriff "Smart-Pointer" besser ;))



  • CStoll schrieb:

    (PS: Mit auto_ptr verbindet man fast instinktiv die gleichnamige Standard-Klasse. Wenn du von einer eigenen Pointer-artigen Klasse redest, trifft es der Begriff "Smart-Pointer" besser ;))

    ok, ich wollte nur so wenig wie möglich erklären und auto_ptr verhält sich in der sache exakt so wie mein smartptr.

    implizit funktioniert die konvertierung überhaupt nicht, egal ob mit copyc'tor oder ohne.

    explizit aufgerufen passiert folgendes:

    // kein copy-c'tor
    template <typename D> operator Pointer<D>() { return Pointer<D>(*this); }
    ...
    test(Pointer<eins>(zwei_zwei)); // warning C4717: 'Pointer<zwei>::operator<eins> Pointer<eins>': Rekursiv für alle Steuerelementpfade. Die Funktion verursacht einen Stapelüberlauf zur Laufzeit.
    

    mit einem copy-c'tor frisst der compiler es, aber beschwert sich dass er nicht auf protected-member von Pointer<zwei> zugreifen darf 😞

    ich würde auch sehr gerne implizit konvertieren lassen.



  • um nochmal darauf zurückzukommen:

    7H3 N4C3R schrieb:

    Übergib doch einfach eine const eins&.

    ich will aber die verwendung (m)eines smartpointers im verwendeten kontext erzwingen.

    Die Übergabe einer const Referenz auf einen auto_ptr ist semantisch eh ziemlich fragwürdig.

    siehe oben, es geht weniger um einleuchtende semantik als darum den programmierer zu seinem glück zu zwingen. es spricht zumindest nichts offensichtliches dagegen, sofern diese konvertierung sauber hinzukriegen ist.



  • ok, ich wollte nur so wenig wie möglich erklären und auto_ptr verhält sich in der sache exakt so wie mein smartptr.

    Nein. Dein Pointer scheint kopierbar zu sein ohne das Original dabei zu verändern. std::auto_ptr ist NICHT kopierbar ohne das Original dabei zu verändern. Das ist ein wichtiger Unterschied und auch der Grund warum "std::auto_ptr<A> const&" nicht implizit in "std::auto_ptr<B> const&" konvertierbar ist, selbst wenn A* implizit in B* konvertierbar ist (upcast).

    implizit funktioniert die konvertierung überhaupt nicht, egal ob mit copyc'tor oder ohne.

    mit einem copy-c'tor frisst der compiler es, aber beschwert sich dass er nicht auf protected-member von Pointer<zwei> zugreifen darf

    Ein template ctor sollte genug sein damit implizite Konvertierung funktioniert:

    template <class T> class foo
    {
    public:
        foo(...);
    
        template <class U> foo(foo<U> const&);
        template <class U> friend class foo; // andere versionen von foo sind friend
    private:
        ...
    };
    

    BTW: du solltest wohl eher private verwenden anstelle von protected, mit protected sind die Members im Prinzip für jeden "offen" der nur will (ich muss mir nur eine eigene Klasse von deiner Klasse ableiten und in meiner Klasse z.B. eine static Helperfunktion reinschreiben, dann kann ich über diese Helperfunktion auf alle protected Members deiner Klasse zugreifen)

    ---

    BTW: jemandem etwas aufzwingen zu wollen ist meist ein Fehler. Vor allem wenn du selbst nicht genug über C++ weisst um dieses Problem zu lösen ... naja.
    Davon abgesehen kann man generische smart pointer in C++ nicht "safe" machen, man kommt immer an den raw pointer dran, z.B. über operator ->. Ergo kannst du auch niemanden dazu zwingen deine smart pointer Klasse zu verwenden.


  • Mod

    Über die Implementierung von auto_ptr kann man Bücher schreiben. Fakt ist, dass auto_ptr mit den Vorgaben des Standards ohne Magie niemals im Stande ist, Copy-Initialisierung von einem auto_ptr auf eine abgeleitete Klasse durchzuführen, da steht 8.5/14 4/2.Alt dagegen, es würde stets eine Konvertierungssequenz mit 2 selbstdefinierten Konvertierungsfunkitionen entstehen, was nicht zulässig ist. Eine Variante per const-Overload in Verbindung mit SFINAE wie hier vorgeschlagen, funktioniert nicht, da der const-Overload spezieller ist und auch alle echten const-Objekte konsumiert (Ich frage mich, mit welchen Compilern der Autor das getestat haben will, bei mir funktioniert es jedenfalls nicht). Letzlich benötigen eine Konvertierungssequenz mit nur einer eigenen Konvertierungsfunktion. Der einzige Konstruktor, der darin noch aufgerufen werden darf, ist der Copy-ctor, folglich muss unser Konvertierungsoperator in auto_ptr& konvertieren:

    template<class T>
    class auto_ptr
    {
    public:
        typedef T element_type;
    
        explicit auto_ptr(T* p = NULL) throw() : p_( p ) {}
        auto_ptr(auto_ptr& other) throw() : p_( other.release() ) {}
        auto_ptr& operator=(auto_ptr& other) throw() { reset( other.release() ); return *this; }
        template<typename U> operator auto_ptr< U >&();
    #ifndef _MSC_VER // Workaround wegen Bug in VC bzgl. 12.3.2/1
    private:
        struct auto_ptr_ref { auto_ptr* p_; };
    public:
        auto_ptr(auto_ptr_ref ref) throw() : p_( ref.p_->release() ) {}
        auto_ptr& operator=(auto_ptr_ref ref) throw() { reset( ref.p_->release() ); return *this; }
        operator auto_ptr_ref() { auto_ptr_ref ref = { this }; return ref; }
    #endif
        ~auto_ptr() throw() { if ( p_ != NULL ) delete p_; }
    
        T& operator*() const throw() { return *get(); }
        T* operator->() const throw() { return get(); }
        T* get() const throw() { return p_; }
        T* release() throw() { T* p = p_; p_ = NULL; return p; }
        void reset(T* p = NULL) throw() { if ( p_ != NULL && p_ != p ) delete p_; p_ = p; }
    
    private:
        T* p_;
    };
    
    struct Base {};
    struct Derived : Base {};
    
    typedef auto_ptr< Base > base_ptr;
    typedef auto_ptr< Derived > derived_ptr;
    
    void foo()
    {
        base_ptr bp;
        const base_ptr cbp;
        derived_ptr dp;
        const derived_ptr cdp;
        base_ptr base_source();
        const base_ptr const_base_source();
        derived_ptr derived_source();
        const derived_ptr const_derived_source();
        void base_sink(base_ptr);
        { base_ptr p = bp; }
        { base_ptr p = dp; }
        base_sink( bp );
        base_sink( dp );
        base_sink(base_source());
        base_sink(derived_source());
    #if 0
        { base_ptr p = cbp; }
        { base_ptr p = cdp; }
        base_sink( cbp );
        base_sink( cdp );
        base_sink(const_base_source());
        base_sink(const_derived_source());
    #endif
    }
    

    Problematisch ist dabei aber die Implementierung des Konvertierungsoperators - (Typisch Meyers EffC++, Don't try to return a reference when you must return an object) - leider bleibt uns hier nichts anderes übrig. Eine naive Implementation:

    template<typename T> template<typename U> auto_ptr<T>::operator auto_ptr<U>&() throw()
    {
        static auto_ptr<U> p;
        p.reset( release() );
        return p;
    }
    

    ist nicht reentrant - selbst mit nur einem Thread knallt es, wenn wir die Funktion mehrfach in einem Ausdruck aufrufen müssen. Wenn wir dynamisch allokieren (mit Flag im auto_ptr, damit im copy-ctor korrekt ein delete ausgeführt wird), riskieren wir exceptions. Möglich wäre auch die Nutzung eine thread-lokalen Ringpuffers, wenn diese groß genug ist,verhindern wir so aliasing-Probleme innerhalb eines Ausdrucks, wirklich schön ist das allerdings auch nicht.
    Wirklich gut wird das erst mit C++0x lösbar sein, dann gibt es ja auch move_ptr & Co.



  • hustbaer schrieb:

    BTW: jemandem etwas aufzwingen zu wollen ist meist ein Fehler. Vor allem wenn du selbst nicht genug über C++ weisst um dieses Problem zu lösen ... naja.

    es geht mir darum zur verwendung schlauer statt nackter pointer zu animieren. die schnittstellen müssen an einigen stellen pointer akzeptieren und ich will nackte pointer mit allen mitteln vermeiden.
    selbst wenn ich nicht genug über c++ weiß um dieses sehr spezielle problem auf anhieb korrekt und sauber zu lösen, wäre es ziemlich bescheuert deswegen diesen anspruch einfach über board zu werfen. statt dessen lasse ich mir lieber auf die sprünge helfen von leuten, die weniger pragmatisch mit c++ umgehen als ich.

    Davon abgesehen kann man generische smart pointer in C++ nicht "safe" machen, man kommt immer an den raw pointer dran, z.B. über operator ->. Ergo kannst du auch niemanden dazu zwingen deine smart pointer Klasse zu verwenden.

    ich will den pointer ja nicht komplett verstecken. ich will wo immer das möglich ist den jeweiligen programmierer dazu zwingen, über die besitzverhältnisse des pointers nachzudenken. sowas ist hilfreich wenn man mit leuten zusammenarbeitet, die in c denken, sowas ähnliches wie c++ schreiben und wo schnell fertig oberste priorität hat. 🙂

    die technischen details lese ich mir ausgeschlafen durch, danke schonmal 🙂


Anmelden zum Antworten