Problem mit Forward Declaration bei selbstgeschriebenem shared_ptr



  • http://blogs.msdn.com/b/vcblog/archive/2011/02/11/10128357.aspx
    🙂

    Eine vernünftige Implementierung lässt auch Konvertierungen zu ... also shared_ptr<T> --> shared_ptr<const T> und shared_ptr<Derived> --> shared_ptr<Base>. Dann gibt es noch die Sache mit weak_ptr. Und da man sowieso für den Referenzzähler etwas dynamisch allozieren muss, kann man auch einen Deleter in Form eines Funktors dranhängen. Und Thread-sicher sollten die Referenzzähler auch sein. Mit anderen Worten: Nimm gleich std::tr1::shared_ptr ...



  • Diese Video über den shared_ptr ist relativ interessant und vom Maintainer der VC++ STL: http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Stephan-T-Lavavej-Advanced-STL-1-of-n
    🙂



  • @wxSkip:
    Der Deleter wird normalerweise im Shared-Count mit abgespeichert, und by-value an den Konstruktor von shared_ptr übergeben.
    Der Default-Deleter wird durch einen Default-Parameter des Konstruktors erzeugt.

    Vereinfacht:

    struct shared_count_base
    {
        size_t m_strong_refs;
        size_t m_weak_refs;
        virtual void DoDelete() = 0;
    };
    
    template <class T, class D>
    struct shared_count : shared_count
    {
        D m_deleter;
    
        shared_count(D d) : m_deleter(d) {}
    
        virtual void DoDelete(T* t)
        {
            m_deleter(t);
        }
    };
    
    template <class T>
    struct shared_ptr
    {
        T* m_t;
        shared_count_base* m_sc;
    
        template <class D>
        static shared_count_base* make_shared_count(T* t, D d)
        {
            std::auto_ptr<T> guard(t);
            shared_count_base* sp = new shared_count<T, D>(d);
            guard.release();
            return sp;
        }
    
        template <class D>
        shared_ptr(T* t, D d = default_deleter<T>())
           : m_t(t), m_sc(make_shared_count(t, d))
        {}
    };
    

    Dass dabei ein bad_alloc fliegen kann kann man nicht verhindern, irgendwoher muss der shared_count ja kommen.

    Damit es nicht zu Memory-Leaks kommt, wird das übergebene Objekt in so einem Fall einfach gelöscht.

    BTW: der übergebene Deleter muss garantiert no-throw kopierbar sein. Wüsste auch nicht wie man das besser machen könnte. (Ein function-try Block fängt ja auch keine Exceptions die beim Kopieren der Parameter auftreten ... oder doch? - ne, kann IMO nicht sein.)



  • hustbaer schrieb:

    Vereinfacht:

    struct shared_count_base
    {
        size_t m_strong_refs;
        size_t m_weak_refs;
        virtual void DoDelete() = 0;
    };
    
    template <class T, class D>
    struct shared_count : shared_count
    {
        D m_deleter;
    
        shared_count(D d) : m_deleter(d) {}
    
        virtual void DoDelete(T* t)
        {
            m_deleter(t);
        }
    };
    

    Außer dem Deleter müsstest Du hier auch noch den Zeiger speichern, damit DoDelete ohne Parameter aufgerufen werden kann. Und shared_count_base braucht noch einen virtuellen Destruktor.



  • @krümelkracker+hustbaer:
    OK, ich habe jetzt auch bemerkt, dass der shared_ptr-Konstruktor doch etwas werfen kann. Es bleibt aber doch die Frage: Glaubt ihr, mein Vorschlag würde funktionieren (zumindest kompiliert er ohne Probleme)? Ich brauche auch nur shared_ptr mit den hier angegebenen Funktionen (und vielleicht Vergleichsoperatoren oder etwas ähnlich einfachem).



  • wxSkip schrieb:

    Glaubt ihr, mein Vorschlag würde funktionieren [...]?

    Nein. Wenn der Typ von *ptr unvollständig ist, dann wird bei einem delete ptr; möglicherweise nicht der richtige delete-Operator aufgerufen und auch keinesfalls irgendein Destruktor.

    wxSkip schrieb:

    (zumindest kompiliert er ohne Probleme)

    Du meinst, abgesehen von der Warnung? :p
    Das fehlerfreie Kompilieren ist keine Garantie dafür, dass es auch funktioniert.



  • krümelkacker schrieb:

    hustbaer schrieb:

    Vereinfacht:

    struct shared_count_base
    {
        size_t m_strong_refs;
        size_t m_weak_refs;
        virtual void DoDelete() = 0;
    };
    
    template <class T, class D>
    struct shared_count : shared_count
    {
        D m_deleter;
    
        shared_count(D d) : m_deleter(d) {}
    
        virtual void DoDelete(T* t)
        {
            m_deleter(t);
        }
    };
    

    Außer dem Deleter müsstest Du hier auch noch den Zeiger speichern, damit DoDelete ohne Parameter aufgerufen werden kann. Und shared_count_base braucht noch einen virtuellen Destruktor.

    Ja, stimmt beides 🙂
    Den Destruktor hab' ich schlicht vergessen.
    Und beim Deleter hatte ich mich davon irreleiten lassen dass in den shared_ptr Instanzen ja ein Zeiger abgespeichert ist. Bloss der muss ja mit dem ursprünglichen T* gar nix mehr zu tun haben.

    (Ich seh grad da sind auch noch ein haufen andere Flüchtigkeitsfehler drin :D)



  • krümelkacker schrieb:

    ...

    ich meinte den Vorschlag mit dem Template-Funktionspointer. Nachlesen!



  • wxSkip schrieb:

    krümelkacker schrieb:

    ...

    ich meinte den Vorschlag mit dem Template-Funktionspointer. Nachlesen!

    Mach mal den reinterpret_cast weg.
    Und den Zeiger im shared_ptr einfach so an den Deleter übergeben ist auch nicht gut. Wenn T* nichtmehr das ursprüngliche T* ist kann das Probleme machen, weil z.B. der falsche Destruktor ausgeführt wird.

    BTW: wo speicherst du denn den Referenz-Zähler? Hast du kein "shared count" Objekt? Verwendest du etwa ne Linked-List statt nem Zähler?



  • hustbaer schrieb:

    wxSkip schrieb:

    krümelkacker schrieb:

    ...

    ich meinte den Vorschlag mit dem Template-Funktionspointer. Nachlesen!

    Mach mal den reinterpret_cast weg.
    Und den Zeiger im shared_ptr einfach so an den Deleter übergeben ist auch nicht gut. Wenn T* nichtmehr das ursprüngliche T* ist kann das Probleme machen, weil z.B. der falsche Destruktor ausgeführt wird.

    BTW: wo speicherst du denn den Referenz-Zähler? Hast du kein "shared count" Objekt? Verwendest du etwa ne Linked-List statt nem Zähler?

    Das mit dem reinterpret_cast habe ich jetzt nur gemacht, weil ich nicht genau wusste, ob das so zulässig ist, ich hätte es mir aber eigentlich denken können.
    Wie soll der T * nicht mehr der ürsprüngliche T * sein? Es kann ja erst ein neues Objekt an der Speicheradresse alloziert werden, wenn das alte gelöscht wurde. Und das ist ja vorher noch nicht der Fall. Ich habe auch nicht vor, irgendwelche krummen Pointer-Hacks zu machen. Dass shared_ptr<Base>(new Derived) den Derived-Destruktor aufruft, auch wenn er nicht virtuell ist, ist hier zwar nicht gegeben, aber ich könnte es noch hinzufügen und außerdem mache ich meine Destruktoren sowieso immer virtuell, wenn ich ihn so benutze.

    Der shared_count ist bei mir durch die ref_count-Map realisiert (ganz oben im Code).



  • wxSkip schrieb:

    krümelkacker schrieb:

    ...

    ich meinte den Vorschlag mit dem Template-Funktionspointer. Nachlesen!

    Was Du mit "mein Vorschlag" gemeint hast, war nicht eindeutig.

    void* -> T* geht auch mit einem static_cast

    Um bei dem Ansatz die Konvertierung shared_ptr<derived> --> shared_ptr<base> zulassen zu können, müsstest Du einen zusätzlichen void* im shared_ptr speichern. Dann hättest Du drei Zeiger:
    - T*
    - void*
    - void()(void)
    Warum müsstest Du das? Weil derived* -> base* -> void* -> derived* nicht garantiert verlustfrei ist (Stichwort pointer adjustments)

    Was hindert Dich den daran, folgendes zu benutzen:

    struct sp_control_block
    {
        size_t m_strong_refs;
        size_t m_weak_refs;
        sp_control_block() : m_strong_refs(0), m_weak_refs(0) {}
        virtual void do_delete() = 0;
        virtual ~sp_control_block() {}
    };
    
    template <class T, class D>
    struct sp_cb_plus_deleter : sp_control_block
    {
        T* m_ptr;
        D  m_deleter;
    
        explicit sp_cb_plus_deleter(T* p, D d = D()) : m_ptr(p), m_deleter(d) {}
    
        virtual void do_delete()
        {
            m_deleter(m_ptr);
            m_ptr = 0;
        }
    };
    
    template<class T>
    struct default_deleter
    {
      void operator()(T* ptr) const {delete ptr;}
    };
    
    template<class T, class D>
    inline sp_control_block* alloc_control_block(T* obj, D del)
    {
      return sp_cb_plus_deleter<T,D>(obj,del);
    }
    
    template<class T>
    inline shared_count_base* alloc_control_block(T* obj)
    {
      return alloc_control_block(obj,default_deleter<T>());
    }
    

    ?

    oder gar std::tr1::shared_ptr?



  • wxSkip schrieb:

    Der shared_count ist bei mir durch die ref_count-Map realisiert

    Damit kommst Du zwar um einen zusätzlichen Zeiger im shared_ptr herum, bedeutet aber auch Nachteile (globales Objekt muss entsprechend geschützt werden falls Du mehrere Threads laufen lässt, und Du kannst keine eigenen Deleter verwenden)



  • 1. Ja, das war nicht eindeutig 🙄 . Ich habe bloß vermisst, dass jemand auf meinen Beitrag/meine Frage eingeht.
    2. "pointer adjustments": Wieder was gelernt 😉
    3. Dein Code entspricht da sicherlich mehr den ganzen Pitfalls. Ich wollte bloß wissen, aus welchen Gründen ich meinen umändern sollte.
    4. Wenn ich das richtig verstanden habe, ist die Map langsamer (auch wenns eine unordered_map ist). D.h. so etwas würde dann nicht funktionieren:

    T *t = new T();
    shared_ptr<T> p1(t);
    shared_ptr<T> p2(t);
    

    Brauche ich aber eigentlich auch nicht.



  • @kk:
    Gegen std::tr1::shared_ptr spricht folgendes:
    1. Wenn's nicht sein muss, will ich nicht unbedingt vom TR1 abhängig sein.
    2. Ich brauche einen shared_ptr, dem man die Aufsicht über das Löschen des Pointers wieder entziehen kann (für GUI-Systeme, wo Parents ihre Children selbst verwalten). So was habe ich beim std::shared_ptr nicht gefunden.

    P.S.: Bin gerade beim Implementieren eurer Vorschläge, melde micht dann nochmal 😉



  • > Ich brauche einen shared_ptr, dem man die Aufsicht über das Löschen des Pointers wieder entziehen kann

    Dann ist der Name "shared_ptr" IMHO unangebracht.



  • krümelkacker schrieb:

    > Ich brauche einen shared_ptr, dem man die Aufsicht über das Löschen des Pointers wieder entziehen kann

    Dann ist der Name "shared_ptr" IMHO unangebracht.

    Dann mach mir einen besseren Vorschlag 😉
    Er ist ja meistens shared, aber eben nicht immer.
    -> Die Aufsicht soll natürlich allen shared_ptr-Objekten, die den selben Pointer beinhalten, entzogen werden.

    EDIT: Beispiel:

    shared_ptr<MyWidget> widget(new MyWidget);
    
    widget.DisableManaging();
    widget->SetParent(window);  //widget wird von window gemanaged
    
    //...
    
    widget->SetParent(NULL);
    widget.EnableManaging();
    


  • wxSkip schrieb:

    Dann mach mir einen besseren Vorschlag

    divided_ptr 🙄
    oder unshared_ptr 🤡



  • HighLigerBiMBam schrieb:

    wxSkip schrieb:

    Dann mach mir einen besseren Vorschlag

    divided_ptr 🙄

    😃

    template<typename T> class divided_ptr;
    
    template<typename T> class ptr
    {
        template<int n> class divided
        {
            typedef divided_ptr<T> type;
        }
    };
    


  • wxSkip schrieb:

    Ich brauche einen shared_ptr, dem man die Aufsicht über das Löschen des Pointers wieder entziehen kann (für GUI-Systeme, wo Parents ihre Children selbst verwalten). So was habe ich beim std::shared_ptr nicht gefunden.

    Du könntest auch "einfach" den Deleter manipulieren.

    Gruß



  • wxSkip schrieb:

    EDIT: Beispiel:

    shared_ptr<MyWidget> widget(new MyWidget);
    
    widget.DisableManaging();
    widget->SetParent(window);  //widget wird von window gemanaged
    
    //...
    
    widget->SetParent(NULL);
    widget.EnableManaging();
    

    Du könnest weiterhin shared_ptr/weak_ptr verwenden, indem Du
    im Child ein weak_ptr zum Parent speicherst und
    im Parent ein shared_ptr zum Child speicherst

    Oder Du erfindest diese ganzen Räder nicht alle nochmal neu und nimmst einfach Qt oder gtkmm.


Anmelden zum Antworten