Rückgabe von 'this' als Smart-Pointer



  • Hallo,

    ich habe gerade eine Denklblockade bei einer Architektur, die den Smart-Pointer std::tr1::shared_ptr verwendet.

    Konkret handelt es sich um einen Interpreter, der aus einer Ausdrucks-Klassen-Hierarchie aufgebaut ist. Die abstrakte Basisklasse besitzt die Methode 'eval'. Diese Methode gibt ein 'shared_ptr<value>' zurück. 'value' selbst erbt von 'expression'. Das heißt, wenn man 'eval' auf einen value ausführt, soll eigentlich eine Referenz auf 'this' zurückgegeben werden. Das ist natürlich aber illegal, weil das Objekt dadurch zu oft zerstört wird. Ich will ungern einen rohen Zeiger zurückgeben, weil es gut sein könnte, dass die 'eval'-Methode für einige Objekte tatsächlich neue Objekte erzeugen muss (das ist sogar der Normalfall). Um diese verwalten zu können, muss ich ja wohl oder übel einen Smart-Pointer zurückgeben.

    Kann jemand das Dilemma für mich lösen?

    Nochmal zur Verdeutlichung, folgende Beispielklassen (ohne Konstruktoren und sonstiges Beiwerk):

    struct value : public expression { /* … */ };
    
    // Variante mit Smart-Pointer:
    
    struct int_value : public value {
        int m_value;
        shared_ptr<value> eval(context& ctx) {
            shared_ptr<value> ret(this);
            return ret;
        }
    };
    
    context ctx;
    int_value my_int(42);
    shared_ptr<value> result = my_int.eval(ctx);
    // BANG: Doppelte Deallokation des aktuellen Objekts.
    
    // Variante ohne Smart-Pointer:
    
    struct int_addition : public binary_expression {
        expression* left;
        expression* right;
    
        value* eval(context& ctx) {
            value* leftvalue = left.eval(ctx);
            value* rightvalue = right.eval(ctx);
            value* ret = new int_value(ctx.as_int(leftvalue)  + ctx.as_int(rightvalue));
            return ret;
        }
    };
    
    context ctx;
    int_value x(42);
    int_value y(23);
    int_addition add(&x, &y);
    value* z = add.eval(ctx);
    // BANG: Speicherleck.
    


  • Probier mal folgendes:

    struct int_value : public value, public std::tr1::enable_shared_from_this<int_value>
    {
       // ...
    };
    


  • Hi Artchie,

    mehrere Dinge.

    Erstens: Danke, klasse. Da hat ja jemand die Arbeit für mich gemacht. 😉

    Zweitens: Denkblockade? Hm, das sieht eher nach einem echten (zum Glück gelösten) Problem aus.

    Drittens: Ich bin mir nicht sicher, dass ich den Quellcode der Klasse verstehe: '_internal_weak_this' wird ja nur default-initialisiert. Woher bekommt es den korrekten Zeigerwert?

    Viertens: Funktioniert das auch, wenn ich nur (!) die Basisklasse 'value' von dieser Hilfsklasse erben lasse? Dann stimmt zwar genaugenommen der Templatetyp für die abgeleiteten Klassen nicht, aber dieser Typ ist für den 'shared_ptr' ja eigentlich (!) nur für die Deallokation wichtig.



  • Drittens:

    _internal_weak_this wird von der shared_ptr Klasse initialisiert, wenn man das Objekt das erste mal einem shared_ptr übergibt (was ja auch laut doku voraussetzung dafür ist dass shared_from_this() funktioniert):

    template<class T> class shared_ptr
    {
    // ...
        template<class Y>
        explicit shared_ptr( Y * p ): px( p ), pn( p ) // Y must be complete
        {
            boost::detail::sp_enable_shared_from_this( pn, p, p );
        }
    // ...
    };
    
    // dieser overload greift zuletzt, wegen "..."
    inline void sp_enable_shared_from_this( shared_count const & /*pn*/, ... )
    {
    }
    
    // dieser overload greift wenn die klasse von boost::enable_shared_from_this<T> abgeleitet ist
    template<class T, class Y> void sp_enable_shared_from_this( shared_count const & pn, boost::enable_shared_from_this<T> const * pe, Y const * px )
    {
        if(pe != 0) pe->_internal_weak_this._internal_assign(const_cast<Y*>(px), pn);
    }
    

    Deswegen muss man auch public von enable_shared_from_this<T> ableiten oder shared_ptr zum friend machen 😉

    Viertens:

    Ja, es reicht wenn du "value" von enable_shared_from_this ableitest, bloss musst du den Returnwert von shared_from_this dann natürlich immer casten. Ich würde es aber so machen, also die Basisklasse von enable_shared_from_this ableiten und casten - ist denke ich die bessere Lösung.
    (Falls du nicht public ableitest müsstest du natürlich mehrere shared_ptr<T> zum friend machen, halt für jede abgeleitete Klasse, sonst haut die implizite Konvertierung nach enable_shared_from_this<value>* nichtmehr hin.)



  • hustbaer schrieb:

    _internal_weak_this wird von der shared_ptr Klasse initialisiert, wenn man das Objekt das erste mal einem shared_ptr übergibt (was ja auch laut doku voraussetzung dafür ist dass shared_from_this() funktioniert):

    Ups, Danke. Habe ich irgendwie überlesen.

    Ich muss aber ehrlich sagen, dass mir das ganze ein wenig missfällt. Ich muss mal schauen, ob ich da nicht vielleicht doch einen anderen Mechanismus der Speicherverwaltung verwende.

    Ja, es reicht wenn du "value" von enable_shared_from_this ableitest, bloss musst du den Returnwert von shared_from_this dann natürlich immer casten.

    Nicht mal: So, wie ich das zur Zeit sehe, bräuchte ich diese Art der Rückgabe lediglich für 'eval', und der Rückgabewert dieser Methode ist sowieso ein Zeiger auf 'value'.



  • Naja, du kannst counted-body (boost::intrusive_ptr) statt counted-handle (boost::shared_ptr) verwenden.
    Dadurch entfällt der ganze shared_from_this() Tanz, andrerseits gibt's dann auch keine weak_ptr mehr.



  • Hi Hustbaer,

    schick. Allerdings würde ich dann tendenziell eher gleich operator new geeignet überladen. Muss mal schauen, was sich da später am besten eignet. Ich arbeite jetzt erst mal mit den referenzgezählten Zeigern weiter, die notwendige Änderung ist dann relativ transparent, d.h. da mache ich mir jetzt noch keinen Kopf drum. Aber ich halte es irgendwie für'n schlechtes Zeichen, wenn ich quasi ganz Zu Anfang schon über so fundamentale Probleme stolpere. 😃 Zeugt irgendwie von einem nicht genügende durchdachten Design.



  • Shared ownership ist immer irgendwie doof, deswegen finden ja auch alle (ok, viele) boost::shared_ptr so geil.
    An einigen Stellen kann aber eben boost::intrusive_ptr auch sehr nützlich sein, bzw. evtl. ganz eigene Konstrukte (Manager Klassen etc.).


Anmelden zum Antworten