Smartpointer, wozu?



  • Welchen vorteil bieten shared_ptr?
    Außer:

    • kein explizites delete
    • keine Memoryleaks bei exceptions

    Ich schrecke tendenziell davor zurück, da shared_ptr intern speicher allokieren. Exceptions hab ich sowieso ausgeschaltet und delete zu schreiben ist auch kein Problem. Was spricht noch dafür bzw. in welchen Situationen kommt man nicht drum rum?

    MfG
    DDR-RAM



  • Es ist nicht nur so, dass man kein delete aufrufen braucht, das wäre auch nicht das Problem, sondern man kann es auch nicht vergessen. Und dies passiert schneller als man denkt; auch ohne Exceptions. Und wenn es nur das übliche ist, wie:

    T* p = new T(...);
        // viel Code
        if( Problem ) // dieser Code wurde erst später eingefügt
            return;   
        // viel Code
        delete p;
    

    Ein weiterer Vorteil, den insbesondere der boost::shared_ptr bietet, ist das Hinzufügen einer beliebigen delete-Funktion. Das ist besonders dann schick, wenn man z.B. mit der Win-API arbeitet (geht sicher auch bei anderen APIs). Zum Beispiel:

    // Bem.: HANDLE ist eine void*
        HANDLE hnd = CreateFile( ... );
        if( hnd == INVALID_HANDLE_VALUE ) return Fehler;
        boost::shared_ptr< void > file( hnd, ::CloseHandle );
        // usw.
        // Zugriff auf das Handle via file.get()
    

    Auch hier wieder der Vorteil, dass das File in jedem Fall wieder geschlossen wird (CloseHandle( HANDLE )), sobald der Scope verlassen wird.

    Gruß
    Werner



  • Zusätzlich kannst du Smartptr schön in deinen Klassen benutzen anstatt rohen Zeigern...

    Du musst dich dann nicht um den Copy-C'tor kümmern, da die SmrtPtr wissen wie sie sich zu kopieren haben.
    Desweiteren kannst du dann so bestimmen was noch beim Kopieren deiner SmrtPtr passieren soll -> Stichwort: Referenzzählung



  • Werner Salomon schrieb:

    Es ist nicht nur so, dass man kein delete aufrufen braucht, das wäre auch nicht das Problem, sondern man kann es auch nicht vergessen. Und dies passiert schneller als man denkt; auch ohne Exceptions. Und wenn es nur das übliche ist, wie:

    T* p = new T(...);
        // viel Code
        if( Problem ) // dieser Code wurde erst später eingefügt
            return;   
        // viel Code
        delete p;
    

    hm, und das? 😉

    T tWithAutomaticStorage(...);
    // viel Code
    if( Problem ) // dieser Code wurde erst später eingefügt
        return;   
    // viel Code
    

    Mir ist schon klar, das man eine delete vergessen könnte. Allerdings konnte ich bis jetzt die übeltäter in der Regel aufspüren und das dauerte noch nicht mal solange. (bitte keine Kommentare zur Projektgröße).

    Ein weiterer Vorteil, den insbesondere der boost::shared_ptr bietet, ist das Hinzufügen einer beliebigen delete-Funktion. Das ist besonders dann schick, wenn man z.B. mit der Win-API arbeitet (geht sicher auch bei anderen APIs). Zum Beispiel:

    // Bem.: HANDLE ist eine void*
        HANDLE hnd = CreateFile( ... );
        if( hnd == INVALID_HANDLE_VALUE ) return Fehler;
        boost::shared_ptr< void > file( hnd, ::CloseHandle );
        // usw.
        // Zugriff auf das Handle via file.get()
    

    Auch hier wieder der Vorteil, dass das File in jedem Fall wieder geschlossen wird (CloseHandle( HANDLE )), sobald der Scope verlassen wird.

    Für Wrapperklassen vielleicht ganz interessant, aber das ist jetzt ja schon fast ein anderes Anwendungsgebiet.

    Freak_Coder schrieb:

    Zusätzlich kannst du Smartptr schön in deinen Klassen benutzen anstatt rohen Zeigern...

    Ja, genau darum geht es prinzipiell.

    Du musst dich dann nicht um den Copy-C'tor kümmern, da die SmrtPtr wissen wie sie sich zu kopieren haben.

    Ja, aber 0815-ptr sind auch CopyConstructable und Assignable.

    Desweiteren kannst du dann so bestimmen was noch beim Kopieren deiner SmrtPtr passieren soll -> Stichwort: Referenzzählung

    Ja genau das macht der smart_ptr ja, deshalb keine delete's 😉

    Also smart_ptr sind fast idiotensicher. (Achtung zyklische abhängigkeiten und exceptions zwischen new expression und constructor ⚠ )
    Mit nem normalen pointer gehört man schon fast zur C-Fraktion.
    Wie gesagt, EH interessiert mich atm nicht, weil ich es deaktiviert habe und für das aktuelle Projekt auch nicht aktivieren werde.

    Was mich besonders an den smart_ptr stört, ist die zusätzliche interne speicher-allokation.
    Ich glaube ich schreibe baue mir nen eigenen SmartPtr.
    Danke für die Antworten.

    MfG
    DDR-RAM



  • @DDR-RAM: "shared_ptr" implementiert wie der Name schon sagt "shared ownership". Es gibt viele Situationen wo es mühsam ist einen einzigen "owner" festzulegen, bzw. wo man sogar gezwungen ist manche Objekte sehr lange am Leben zu lassen, bloss weil es in manchen seltenen Fällen sein könnte dass sie noch irgendwo referenziert werden. In solchen Fällen ist es halt sehr praktisch "shared ownership" zu haben. "intrusive_ptr" ist im Prinzip dasselbe, bloss funktioniert "intrusive_ptr" nur wenn im Objekt selbst eine Möglichkeit existiert Referenzen zu zählen. "spared_ptr" kommt ohne das aus, allerdings zahlt man dafür den Preis einer zusätzlichen dynamischen Speicheranforderung.

    EDIT: verwenden doch intrusive_ptr. Natürlich kannst du auch eine "linked list" Implementierung eines smart_ptr machen, die dürfte ohne Multithreading sogar schneller sein, mit allerdings sicher langsamer.

    Ausser dem was "intrusive_ptr" macht und eben der "linked list" Version wüsste ich aber keine Möglichkeit auf die zusätzliche Speicheranforderung zu verzichten.



  • ich glaube du suchst boost::scoped_ptr



  • hustbaer schrieb:

    @DDR-RAM: "shared_ptr" implementiert wie der Name schon sagt "shared ownership". Es gibt viele Situationen wo es mühsam ist einen einzigen "owner" festzulegen, bzw. wo man sogar gezwungen ist manche Objekte sehr lange am Leben zu lassen, bloss weil es in manchen seltenen Fällen sein könnte dass sie noch irgendwo referenziert werden. In solchen Fällen ist es halt sehr praktisch "shared ownership" zu haben.

    An library-grenzen?

    "intrusive_ptr" ist im Prinzip dasselbe, bloss funktioniert "intrusive_ptr" nur wenn im Objekt selbst eine Möglichkeit existiert Referenzen zu zählen.

    Ich werd sowas wahrscheinlich als front-end verwenden.

    "spared_ptr" kommt ohne das aus, allerdings zahlt man dafür den Preis einer zusätzlichen dynamischen Speicheranforderung.

    Also shared_ptr sind nicht schlechtes, mit weak_ptr zusammen hört sich das fast nach gratis gc an, bei der der Entwickler viel anpassen kann durch den spezifischen finalizer. Für Basisklassenzeiger fände ich es aber auch ganz nett, wenn sie die Fähigkeiten eines shared_ptr hätten, Referenzzählung und finalisierung kann man ja auch irgendwie in dieser Klasse erledigen.
    Was macht eigentlich std::auto_ptr?

    MfG
    DDR-RAM



  • #include <memory>
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    int main(int argc, char* argv[])
    {
      auto_ptr<string> a(new string("Hallo!"));
    
      cout << "a zeigt auf: " << a.get() << endl;
    
      auto_ptr<string> b = a;
    
      cout << "a zeigt auf: " << a.get() << endl;
      cout << "b zeigt auf: " << b.get() << endl;
    
      return 0;
    }
    

    Ergebnis:

    a zeigt auf: 003212A8
    a zeigt auf: 00000000
    b zeigt auf: 003212A8
    

    auto_ptr gibt immer die Verantwortung weiter, soll heißen es gibt immer nur einen Besitzer. Also das genaue Gegenteil von shared_ptr.


  • Mod

    auto_ptr dient primär einem einzigen Zweck: als sichere Variante der Rückgabe von dynamisch allokierten Objekten durch Funktionen (oder als Übergabeparameter, falls Ownership transferiert werden soll). Für alles andere gibt es bessere Alternativen. Insbesondere als Klassenmember ist auto_ptr nicht geeignet. Grundsätzlich sollte ohnehin die einfachste Art von Smartpointer, die eine gestellte Aufgabe erfüllen kann, gewählt werden. Das ist im Regelfall scoped_ptr bzw. scoped_array.



  • camper schrieb:

    Grundsätzlich sollte ohnehin die einfachste Art von Smartpointer, die eine gestellte Aufgabe erfüllen kann, gewählt werden. Das ist im Regelfall scoped_ptr bzw. scoped_array.

    Wobei auto_ptr genauso so einfach ist. Sofern nichts kopiert wird, degeneriert dieser zu einem scoped_ptr.



  • Ich finde bloß den Namen "scoped_ptr" aussagekräftiger. Der Name "auto_ptr" suggeriert mehr als er kann. Ist aber rein psychologisch. 🙂


  • Mod

    groovemaster schrieb:

    Wobei auto_ptr genauso so einfach ist. Sofern nichts kopiert wird, degeneriert dieser zu einem scoped_ptr.

    Das ist widersprüchlich. Die Fähigkeit, in bestimmter Weise kopierbar zu sein, verliert der auto_ptr ja nicht dadurch, dass man keinen Gebrauch davon macht. Auch z.B. ein intrusive_ptr verhält sich da nicht anders, wenn (hier kommt es wieder) der interne Referenzcounter des Objekts für keinen anderen Zweck genutzt wird. Es kommt wohl darauf an, was man unter einfach subsumieren will. Jedenfalls schweift das etwas vom Thema ab.



  • camper schrieb:

    Die Fähigkeit, in bestimmter Weise kopierbar zu sein, verliert der auto_ptr ja nicht dadurch, dass man keinen Gebrauch davon macht.

    Das ist richtig. Nur entsteht dabei kein Overhead, wenn die Kopiersemantik nicht genutzt wird. Das meinte ich mit "einfach".



  • DDR-RAM schrieb:

    "spared_ptr" kommt ohne das aus, allerdings zahlt man dafür den Preis einer zusätzlichen dynamischen Speicheranforderung.

    Also shared_ptr sind nicht schlechtes, mit weak_ptr zusammen hört sich das fast nach gratis gc an, bei der der Entwickler viel anpassen kann durch den spezifischen finalizer.

    Oha, Vorsicht. shared_ptr ist weit davon entfernt "gratis" zu sein. Jedesmal wenn du einen nicht-leeren shared_ptr konstuierst oder zerstörst kostet das ein InterlockedIncrement/InterlockedDecrement, und interlocked instructions sind teuer. Alleine das herrumreichen eines shared_ptr by Value ist also schon teuer genug, wobei man das ja noch verhindern kann indem man einfach ne const-ref auf den shared_ptr übergibt.

    Aber versteh' mich nicht falsch, ich verwende shared_ptr häufig, einfach weil die Dinger so wahnsinnig praktisch sind. Bei Programmteilen die hoch optimiert sein müssen (weil sie so oft durchlaufen werden) überlege ich mir aber gut ob ich nicht vielleicht auf shared_ptr verzichten kann, oder einen Weg finden wie ich keine Kopien anlegen muss, bzw. eben so wenig wie möglich.


Anmelden zum Antworten