Smartpointer



  • Vorher hab ich behauptet, dass du EINE map für ALLE Typen benötigst.

    std::map<void *, unsigned int> referencecounter; // ausserhalb des Klassentemplates
    

    Aber sogar das wird nicht funktionieren, wenn Klassen mit Mehrfachvererbung im Spiel sind. Folglich wird das mit der std::map NIE gehen, wenn du Konstruktion aus Zeigern erlaubst (und das sollst du ja).



  • gamer8o4 schrieb:

    1. Du returnst einen pointer auf die komplette map. ich glaube du wolltest eigentlich nur einen pointer AUS DER MAP heraussuchen oder?

    Nein, ich wollte den Pointer auf die map, um zu testen, wieviele maps es eigentlich gibt. Es gibt eine map pro Typ, obwohl die map in mehreren Übersetzungseinheiten definiert wird. Es gibt sicherlich irgendwo eine Sonderregel, die sagt, dass statische Membervariablen eines Klassentemplates beliebig oft definiert werden dürfen und es sie trotzdem nur einmal gibt.

    gamer8o4 schrieb:

    3. wenn ich dich richtig verstanden habe müsste das geben, aber ich würde es lassen, dass hätte keinen sinn. dann müsstest du die map static machen ...

    Die ist doch schon static.

    gamer8o4 schrieb:

    edit: so ich hab mir jetzt mal deinen code durchgelesen, äähm ich glaube du hast das noch nicht so ganz verstanden. shared_ptr ist nur ein zähler! also hat der intern nur einen zähler (unsigned int) und löscht dein object wenn der zähler 0 ist. bei jedem neuen pointer auf das object wird der zähler einen hoch und bei immer wenn einer dieser externen zugriffspointer nicht mehr gebraucht wird mit 1 subtrahiert. lass den map-quatsch, das ist absoluter unsinn

    Genau das tut die map doch. Für jeden Typ wird eine map angelegt. Für jede Referenz wird ein unsigned int angelegt und pro Referenz hochgezählt.

    smarttroll schrieb:

    Die meisten Shared-Pointer allokieren pro Zeiger einen int als Referenzzähler und wenn der 0 wird, wird alles gelöscht.

    Genau das tu ich doch auch. Habe ich da was übersehen?

    smarttroll schrieb:

    Stell dir vor du einen std::unique_ptr auf einen Typ, der non-movable und non-copyable ist. Es ist zwar möglich mit .release() einen Zeiger darauf zu bekommen, aber den kriegt man den unmöglich in deinen SharedPointer. Daher brauchst du die Möglichkeit, von einem Zeiger zu erstellen.

    Stimmt, das hatte ich nicht bedacht.

    static Smartpointer<T> adopt(T *t){
    		Smartpointer<T> *st = static_cast<Smartpointer<T> *>(::operator new(sizeof st));
    		st->target = t;
    		Smartpointer<T> lst(*st);
    		::operator delete(st);
    		return lst;
    	}
    

    Das würde das Problem lösen, ist aber ziemlich hässlich. Statischen Speicher per new anfordern ist nicht so toll, dann void * um den Konstruktor zu umgehen, dann werden unnötigerweise 2 Kopien erstellt (wahrscheinlich kann das auch nicht vom Compiler wegoptimiert werden wegen der Referenzcounter im Copy-Konstruktor / Destruktor). Außerdem treten damit genau die Probleme auf, die ich mit meinen Konstruktoren vermeiden wollte. Naja nicht alle, immerhin kann man einen Pointer beliebig oft adoptieren ohne dass es ein doppeltes delete gibt. Dafür werde ich mir noch was schlaues einfallen lassen müssen.

    smarttroll schrieb:

    Im übrigen wird dein Ansatz von std::make_shared (und bald std::unique_ptr) verfolgt, dann braucht man im Code auch kein new schreiben. Von einem Zeiger zu erstellen ist auch dort die Ausnahme, aber möglich muss es sein.

    Wenn ich C++11 benutzen könnte würde ich das Ganze wahrscheinlich nicht machen.

    smarttroll schrieb:

    Aber es ist schlicht falsch, weil ein Smartpointer<Base> und ein Smartpointer<Derived> einen gemeinsamen Counter brauchen obwohl sie unterschiedliche Typen sind.

    Hmm stimmt. Ich habe jetzt eine std::map<void *, unsigned int> außerhalb definiert und noch einen Konstruktor hinzugefügt damit das mit dem automatischen downcast klappt:

    template <class U> inline Smartpointer(Smartpointer<U> &sp){
    		target = &(*sp);
    		referencecounter[target]++;
    	}
    

    Ich habe das mal bei Ideone hochgeladen.
    Das mit der Mehrfachvererbung muss ich mir nochmal ansehen.



  • Na, den ganzen Ansatz mit der Map sollst du verwerfen. Hier ist das was ich meinte, als spartanischer Shared-Pointer in 10 Zeilen auf Ideone.

    Was ich meinte, wenn du schon einen Smartpointer nachprogrammierst würde ich wenigstens C++11-conform bleiben. Das heisst deine pseudovariadischen Konstruktoren als freie Funktion make_shared schreiben, dann kannst du im Optimalfall ohne Codeänderung auf C++11 wechseln. Ausserdem hat das den Vorteil, dass andere deinen Code auf Anhieb verstehen, weil sie C++11 kennen. In diesem Falle würde ich Boost heranziehen, denn boost::shared_ptr entspricht so ziemlich std::shared_ptr und läuft auch im alten C++.


  • Mod

    Mal kurz drübergeschaut und mit Kommentaren versehen bzw. modifiziert.

    template <class T, bool = is_polymorphic<T> > class Smartpointer{
    // Im nicht-polymorphen Fall müssen statischer und dynamischer Typ
    // sowieso übereinstimmen (wegen delete), folglich kann es auch nicht
    // zu Aliasing mit anderen Smartpointertypen kommen, eine
    // per-Klassen-map ist verwendbar
    public:
        //TODO: Fix constructors with a variadic template some day
        inline Smartpointer()
          : target( new T )  // evtl. new T() in Erwägung ziehen
        {
            incref();
        }
    // ... eine Fall für BOOST_PP...
        template <class A1, class A2, class A3, class A4, class A5,
        class A6, class A7, class A8, class A9, class A10>
        inline Smartpointer(const A1 &a1, const A2 &a2, const A3 &a3,
        const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7,
        const A8 &a8, const A9 &a9, const A10 &a10){
          : target( new T(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) )
        {
            incref();
        }
    
        inline Smartpointer(const Smartpointer &sp)
          : target( sp.target )
        {
            incref();
        }
    
        inline Smartpointer(T* p)
          : target( p )
        {
            incref();
        }
        inline Smartpointer<T> & operator = (const Smartpointer<T> &sp){
    // die Variante vorher vergessen wir ganz schnell
            sp.incref();
            decref();
            target = sp.target;
            return *this;
        }
        inline bool operator < (const Smartpointer<T> &sp) const{
            return std::less<T*>()( target, sp.target ); // std::less-Magie, da op < zu UB führt
        }
        //same for <=, >, >=, ==, !=
        inline T * operator -> (){
            return target;
        }
        inline const T * operator -> () const{ // zweifelhaft
            return target;
        }
        inline T & operator * (){
            return *target;
        }
        inline const T & operator * () const{ // ditto
            return *target;
        }
        inline ~Smartpointer(){
            decref();
        }
    private:
        void incref() const{
            ++referencecounter[target];
        }
        void decref() const{
            if ( !--referencecounter[target] )
                delete target;
        }
        T *target;
        static std::map<const T *, unsigned int> referencecounter;
    };
    
    template <class T> std::map<const T *, unsigned int> Smartpointer<T>::referencecounter;
    
    std::map<const void*, unsigned int> polymorphic_referencecounter;
    
    template <class T> class Smartpointer<T, true>{
    // Im polymorphen Fall benötigen wird eine globale map
    public:
        //TODO: Fix constructors with a variadic template some day
        inline Smartpointer()
          : target( new T )  // evtl. new T() in Erwägung ziehen
        {
            incref();
        }
    // ... eine Fall für BOOST_PP...
        template <class A1, class A2, class A3, class A4, class A5,
        class A6, class A7, class A8, class A9, class A10>
        inline Smartpointer(const A1 &a1, const A2 &a2, const A3 &a3,
        const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7,
        const A8 &a8, const A9 &a9, const A10 &a10){
          : target( new T(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) )
        {
            incref();
        }
    
        inline Smartpointer(const Smartpointer &sp)
          : target( sp.target )
        {
            incref();
        }
    
        inline Smartpointer(T* p)
          : target( p )
        {
            incref();
        }
        template <typename U>
        Smartpointer(const Smartpointer<U>& other)
          : target( other.target )
        {
            incref();
        }
        inline Smartpointer<T> & operator = (const Smartpointer<T> &sp){
    // die Variante vorher vergessen wir ganz schnell
            sp.incref();
            decref();
            target = sp.target;
            return *this;
        }
        inline bool operator < (const Smartpointer<T> &sp) const{
            return std::less<const void*>()( dynamic_cast<const void*>(target), dynamic_cast<const void*>(sp, target) ); // std::less-Magie, da op < zu UB führt
        }
        //same for <=, >, >=, ==, !=
        inline T * operator -> (){
            return target;
        }
        inline const T * operator -> () const{ // zweifelhaft
            return target;
        }
        inline T & operator * (){
            return *target;
        }
        inline const T & operator * () const{ // ditto
            return *target;
        }
        inline ~Smartpointer(){
            decref();
        }
    private:
        void incref() const{
            ++polymorphic_referencecounter[dynamic_cast<const void*>(target)];
        }
        void decref() const{
            if ( !--referencecounter[dynamic_cast<const void*>(target)] )
                delete target;
        }
        T *target;
        template <typename U>
        friend class Smartpointer<U, true>;
    };
    

    Ungetestet, sollte aber einigermaßen korrekt sein. Unterirdische Perfomance ist allerdings unvermeidbar.



  • Camper, ich habe ungeheuren Respekt vor dir und es ist schon spät, deshalb verzeihe Fehler meinerseits. Aber kannst du deine Aussage noch etwas erkläutern?

    camper schrieb:

    Im nicht-polymorphen Fall müssen statischer und dynamischer Typ sowieso übereinstimmen (wegen delete)

    IMHO ist

    int main()
    {
      struct Base {};
      struct Derived : Base {};
    
      Smartpointer<Base> b;
      {
        Smartpointer<Derived> d;
        b = d;
      }
      std::cout << '\n';
      std::terminate();
    }
    

    ein wohldefiniertes Programm, das auch eine globale map benötigt.



  • @nwp3: Das mit der Mehrfachvererbung ist doch kein Problem, wenn du wie camper dynamic_cast<void*> verwendest (hab ich wieder was neues gelernt :)).


  • Mod

    smarttroll schrieb:

    Camper, ich habe ungeheuren Respekt vor dir und es ist schon spät, deshalb verzeihe Fehler meinerseits. Aber kannst du deine Aussage noch etwas erkläutern?

    camper schrieb:

    Im nicht-polymorphen Fall müssen statischer und dynamischer Typ sowieso übereinstimmen (wegen delete)

    IMHO ist

    int main()
    {
      struct Base {};
      struct Derived : Base {};
    
      Smartpointer<Base> b;
      {
        Smartpointer<Derived> d;
        b = d;
      }
      std::cout << '\n';
      std::terminate();
    }
    

    ein wohldefiniertes Programm, das auch eine globale map benötigt.

    Machiavelli.



  • Was ist denn jetzt mit Machiavelli gemeint?


  • Mod

    auf einem deutsch bitte schrieb:

    Was ist denn jetzt mit Machiavelli gemeint?

    Es hat keinen Sinn, sich gegen vorsätzlichen Mißbrauch der Regeln verteidigen zu wollen.
    Und smarttroll muss mein Argument verstanden haben, um diesen Code produziert haben zu können.



  • camper schrieb:

    Machiavelli.

    Wie wärs dann mit Murphy?

    struct Base {};
    struct Derived : Base {};
    
    void f(Smartpointer<Base> const& p) {}
    
    int main()
    {
      Smartpointer<Derived> d;
      f(d);
    }
    

  • Mod

    smarttroll schrieb:

    camper schrieb:

    Machiavelli.

    Wie wärs dann mit Murphy?

    struct Base {};
    struct Derived : Base {};
    
    void f(Smartpointer<Base> const& p) {}
    
    int main()
    {
      Smartpointer<Derived> d;
      f(d);
    }
    

    Genau. Das Smartpointerargument ist entweder unnötig und also eine Lüge oder aber notwendig und führt dann zu UB.
    Anders gesagt: Smartpointer<Derived>->Smartpointer<Base> ist nur dort sicher, wo Smartpointer<Base> nicht "smart" sein kann/darf.
    std::shared_ptr unterstützt custom_deleters gerade deswegen, weil eine solches Element sowieso erforderlich ist, um Pointerkonvertierungen im allgemeinen Fall zu unterstützen - und die Möglichkeit der Verallgemeinerung keinen zusätzlichen Abstraktionsoverhead mit sich bringt.


Anmelden zum Antworten