Polymorpher Smartpointer



  • dot schrieb:

    Die oben genannten Smart Pointer haben kein gemeinsames Konzept

    Doch, schon. Alle vermitteln irgendeine Art von Besitz, und bei keinem muss man sich um die Freigabe kümmern.

    Trotzdem, ich halte es für sinnvoller sich über den Scope seines Objekts Gedanken zu machen. Was Threads angeht (wo auch immer die plötzlich herkommen oO) würde ich gerne mal ein Beispiel sehen, bei dem das große Vorteile gegenüber shared_ptr hat.

    314159265358979 schrieb:

    Ich schreib hier jetzt sicher keine multi-threaded Anwendung.

    Ja nun, irgendeinen Fall solltest du doch im Kopf haben?



  • cooky451 schrieb:

    dot schrieb:

    Die oben genannten Smart Pointer haben kein gemeinsames Konzept

    Doch, schon. Alle vermitteln irgendeine Art von Besitz, und bei keinem muss man sich um die Freigabe kümmern.

    Nein. Ein weak_ptr z.B. besitzt nichts.



  • cooky451 schrieb:

    dot schrieb:

    Die oben genannten Smart Pointer haben kein gemeinsames Konzept

    Doch, schon. Alle vermitteln irgendeine Art von Besitz, und bei keinem muss man sich um die Freigabe kümmern.

    Nein, weak_ptr vermittelt keinen Besitz sondern ist nur ein Verweis auf etwas, was vielleicht noch von einem shared_ptr gehalten wird.



  • Ich implementiers einfach mal, vlt wird dann klarer, wie das Ding funktioniert.

    template <typename T>
    struct unknown_ptr
    {
        struct ptr_wrapper_base
        {
            virtual T&       operator * ();
            virtual T const& operator * () const;
    
            virtual T*       operator -> ();
            virtual T const* operator -> () const;
    
            virtual ~ptr_wrapper_base() {}
        };
    
        template <typename Ptr>
        struct ptr_wrapper : ptr_wrapper_base
        {
            Ptr ptr;
    
            ptr_wrapper(Ptr ptr)
                ptr(std::move(ptr))
            {}
    
            virtual T&       operator * ()       { return *ptr; }
            virtual T const& operator * () const { return *ptr; }
    
            virtual T*       operator -> ()       { return &*ptr; }
            virtual T const* operator -> () const { return &*ptr; }
        };
    
        T&       operator * ()       { return **ptr; }
        T const& operator * () const { return **ptr; }
    
        T*       operator -> ()       { return &**ptr; }
        T const* operator -> () const { return &**ptr; }
    
        template <typename Ptr>
        unknown_ptr(Ptr ptr)
            : ptr(new ptr_wrapper<Ptr>(std::move(ptr)))
        {}
    
    private:
        std::unique_ptr<ptr_wrapper_base> ptr;
    };
    


  • Und ist klar was du machen willst. Und ist aber nicht klar warum.



  • Ich will ein Objekt an eine Funktion übergeben. Wo dieses Objekt herkommt, ist egal. Wichtig ist, dass das Objekt ggf. am Leben gehalten werden muss. Deshalb kann ich keinen rohen Zeiger/keine Referenz verwenden.

    Ein konkretes Beispiel hab ich im Moment leider nicht, ist schon ein paar Tage her, dass ich sowas wollte und ich habs mittlerweile vergessen.



  • Wie sieht's mit shared_ptr mit Deleter aus?



  • @314159265358979
    shared_ptr erfüllt deine Vorgaben, bis auf zwei: er ist zusätzlich noch kopierbar, und er kann nie "weak" sein.
    Die Kopierbarkeit kannst du leicht entfernen, indem du ihn in eine eigene Klasse einwickelst.

    Dann brauchst du noch drei verschiedene Konstruktoren: 1x für nicht besitzend, 1x für shared und 1x für unique.
    Nicht besitzend initialisiert den internen shared_ptr mit einem Dummy-Deleter, shared kopiert einfach den übergebenen shared_ptr und unique erzeugt einen neuen shared_ptr, der ausserhalb des Wrapper-Objekts nicht zugänglich ist, d.h. auch nicht kopiert werden kann = unique ownership.

    Wenn das Ding unbedingt auch "weak" sein können soll, dann könntest du nen variant<shared_ptr, weak_ptr> in der Wrapper-Klasse verwenden.

    Dafür müssen sich dann alle User der Klasse damit rumschlagen den Fall "Objekt ist weg" irgendwie behandeln zu müssen.

    Das Warum ist mir aber auch nicht klar, ich hatte für sowas noch keine Anwendung.

    Einzig bei Strings hätte ich mir was ähnliches mal gewünscht: eine lightweight "String-Referenz" Klasse die nicht besitzend sein muss (aber kann). Und zwar für (fast) ausschliesslich eine Anwendung, nämlich als Funktions-Parameter. Damit könnte man Funktionen machen die folgende Dinge können:
    * Ohne strlen() die Grösse ermitteln, daher auch...
    * ...Strings mit eingebetteten Null-Charactern verarbeiten
    * Ohne .c_str() mit std::(w)string kompatibel sein (über passenden Conversion-Ctor)
    * Ggf. den String "klauen" (move) können, wenn der "String-Ref" Parameter besitzend ist, bzw. wenn nicht einfach eine Kopie machen

    Und das alles mit minimalem Overhead (zumindest minimal im Vergleich zu "einfach std::string verwenden", wo dann jedesmal aus einem Literal ein std::string Parameter erzeugt werden muss).

    Zusätzlich würde die Klasse natürlich alle Typedefs und const-Funktionen von std::string anbieten.



  • 314159265358979 schrieb:

    Wichtig ist, dass das Objekt ggf. am Leben gehalten werden muss.

    Okay, lass uns damit mal einen Blick auf die Liste der möglichen Kandidaten werfen, die du vorhin gepostet hast:

    314159265358979 schrieb:

    Da kann [...] theoretisch auch ein roher, nicht besitzender Zeiger drin sein.

    314159265358979 schrieb:

    Deshalb kann ich keinen rohen Zeiger/keine Referenz verwenden.

    Was denn nu? Für deinen Problem fall würde ich eher letzteres annehmen - du kannst bei einem rohen Zeiger nicht sicher gehen, dass das Objekt deine Funktion überlebt.

    314159265358979 schrieb:

    Da kann ein [...] some_other_ptr [...] drin sein.

    Ist some_other_ptr nur ein Platzhalter für einen x-beliebigen Smartpointer oder schwebt dir was konkretes vor?

    314159265358979 schrieb:

    Da kann ein [...] weak_ptr [...] drin sein.

    weak_ptr garantiert dir aber nicht, dass das Objekt die Funktion überlebt. Du kannst auf einen weak_ptr nichtmal zugreifen. Du musst erst einen shared_ptr draus machen, und das möglichst schon beim Funktionsaufruf.

    314159265358979 schrieb:

    Da kann ein [...] shared_ptr [...] drin sein.

    Jup.

    314159265358979 schrieb:

    Da kann ein unique_ptr [...] drin sein.

    Den müsstest reinmoven.

    Zusammengefasst: mal abgesehen vom some_other_ptr und dem unsicheren rohen Pointer werden alle Fälle vom shared_ptr abgedeckt. Und wenn dir die Konvertierung unique_ptr->shared_ptr ein Dorn im Auge ist gibts Abhilfe:

    class DoFunc { 
      private: 
      R operator()(T* raw_ptr) const
        { /* do stuff */ }
      friend R f(std::unique_ptr<T>);
      friend R f(std::shared_ptr<T>);
    };
    
    R f(std::unique_ptr<T> uptr) {
      DoFunc df; return df(uptr.get());
    }
    R f(std::shared_ptr<T> sptr) {
      DoFunc df; return df(sptr.get());
    }
    


  • Ich bin immer noch der Meinung dass der Grund wieso er überhaupt meint sowas zu brauchen ein Designfehler ist.

    314159265358979 schrieb:

    Ich will ein Objekt an eine Funktion übergeben. Wo dieses Objekt herkommt, ist egal. Wichtig ist, dass das Objekt ggf. am Leben gehalten werden muss.

    Statt da irgendwas mit Smart Pointern zusammenzuhacken, solltest du imo lieber die Besitzverhältnisse deiner Objekte klären.


Anmelden zum Antworten