Speicherverwaltung mit boost::shared_ptr



  • Als allererstes:
    Ich wusste jetzt nicht genau, wohin ich das Thema schreiben soll, da es um etwas Designtechnisches in C++ geht. Demnach kamen eben dieses Forum und "Rund um die Programmierung" infrage. Ich habe mich dann für das C++ Forum entschieden.

    Es geht um Pointer und Speicherverwaltung mit boost::shared_ptr.

    Folgendes: Ich habe eine Klasse A. Um einen Pointer auf die Klasse zu erstellen, habe ich folgendes Layout gebastelt:

    // Text ist nur ein Dummy
    class A
    {
    public:
    
      typedef boost::shared_ptr<A> Ptr;
    
      A::Ptr create(std::wstring text);
    
      std::wstring getText() const { return this->text; }
    
    private:
    
      A(std::wstring text);
    
      std::wstring text;
    };
    
    A::Ptr A::create(std::wstring text)
    {
      return A::Ptr(new A(text));
    }
    
    // Anwendung:
    A::Ptr a = A::create(L"Hallo");
    

    Wie man sieht, ist der Konstruktor von A private. Um eine Instanz von A zu erstellen, muss man die öffentliche Methode create() aufrufen, die ein A::Ptr Objekt zurückgibt. Das ist letztlich nichts anderes, als ein normaler Pointer, der in einem shared_ptr<A> liegt. So kommt es nicht zu Speicherlecks.

    Mit dieser Methode ist es auch nicht mehr möglich, auf den Konstruktor von A zuzugreifen. D.h. sowas wie

    A* a = new A(L"Hallo");
    

    ist nicht mehr möglich.

    Nunja, nun habe ich aber mit einem Freund über dieses System diskutiert und wir kamen zu keinem Ergebnis. Er ist der Meinung, man könnte das ganze auch über

    A::Ptr a = boost::shared_ptr(new A(L"Hallo"));
    

    schreiben. Ihm gefällt die Variante mehr, da er auf Anhieb sieht, was passiert. Das mag vielleicht Geschmackssache sein. Trotzdem ist in seiner Variante der Konstruktor öffentlich, weshalb es zu Speicherlecks kommen kann, wenn mal falsch ge'new't wird, obwohl mein Freund dazu nur sagt, dass "jeder ja wohl selbst entscheiden kann, ob er mit new arbeiten will oder nicht."

    Dafür spricht allerdings, dass eine Klasse ja eigentlich nichts mit der eigenen Speicherverwaltung zu tun haben sollte und eine create-Methode demnach nicht in die Klasse gehört.

    Deswegen jetzt meine Frage:
    Was ist objektiv gesehen die bessere Lösung? Gibt es vielleicht besondere Anwendungsfälle, die bislang nicht beachtet wurden? Wie seht ihr das?



  • objektiv gesehen, hängt das vom anwendungsfall ab.
    d.h., wenn du unbedingt verhindern willst, dass deine Klasse auf dem Stack liegt, musst du so vorgehen, wie du bechreibst. Dadurch kann man keine objekte mehr selber erstellen. Das ist nötig, wenn man zB eine Riesenklasse hat, die auch über scopegrenzen hinweg leben soll, wie zB die windows bei wxwidgets.

    anderereseits ist es nervig, immer erst so ein create aufzurufen, da hat dein Freund schon recht. Das sieht sogar von außen wie singletonpattern aus, obwohls das nicht ist.

    Mein Rat: Ich würde die offenere Methode verwenden. wie gesagt, man muss nun mal als c++-programmierer selber auf den Speicher achten. Wer einen GC braucht, kann zu java oder c# gehen.

    Ist aber nur meine meinung und hängt von anwendungsfall ab und ich bin auch nicht der oberchecker (wie zB camper). 🙂



  • hans im unglück schrieb:

    Ich wusste jetzt nicht genau, wohin ich das Thema schreiben soll, ...

    Du bist hier schon ganz richtig.

    In der Klasse A muss die Methode create static sein, sonst compiliert es gar nicht. Zudem solltest Du noch den Copy-Konstruktor und den Zuweisung-Operator privat machen; sonst geht z.B. auch

    A::Ptr a = A::create(L"Hallo");
        A* b = new A( *a );
    

    hans im unglück schrieb:

    Deswegen jetzt meine Frage:
    Was ist objektiv gesehen die bessere Lösung? Gibt es vielleicht besondere Anwendungsfälle, die bislang nicht beachtet wurden? Wie seht ihr das?

    Eine bessere Lösung gibt's nicht; wie Maxi das schon ausgeführt hat. Es kommt drauf an, was Du erreichen willst.

    Die Lösung mit so einer Factory-Methode (so würde man das create dann nennen) bietet sich insbesondere dann an, wenn noch ein mehr oder weniger aufwendiger Code notwendig wäre um aus dem Text 'text' heraus den Konstruktor aufzurufen. Etwa beim Lesen von Parametern aus einer Datei.
    Ein anderer Anwendungsfall für eine Factory-Methode besteht dann, wenn A eine Basisklasse ist und innerhalb von create eine konkrete Klasse in Abhängigkeit des Inhalts von 'text' erzeugt wird.

    Gruß
    Werner



  • Wenn die Klasse von der Verwendung von shared_ptr unabhängig ist, dann mach keine Factory Funktion.

    Wenn die Klasse notwendigerweise "in einem shared_ptr leben muss" (z.B. weil du intern "shared_from_this" verwendest, was IMO schlechter Stil ist solange man es nicht unbedingt braucht), dann kann man über eine Factory Funktion nachdenken -- z.B. einfach um "Unfälle" zu vermeiden wenn jemand eben KEINEN shared_ptr verwendet.

    Und wenn du den shared_ptr schon in der Konstruktionsphase brauchst (z.B. um ihn irgendwo zu registrieren/an irgendwas zu übergeben), dann macht auf jeden Fall eine Factory Funktion die genau das übernimmt (interne 2 phase construction - sollte nach aussen aber IMO nicht sichtbar sein).

    BTW: shared_ptr schützt nicht gegen Leaks, sondern öffnet nur neue kreative Möglichkeiten welche zu erzeugen 😉

    Und: wenn du schon shared_ptr "erzwingen" willst, dann mach auf jeden Fall auch den dtor private (dazu entweder "friend void boost::checked_delete(T*)" oder nen custom deleter für den shared_ptr machen). Dadurch verhinderst du zumindest mal ein "delete sp.get();".


Anmelden zum Antworten