C++: keine Bibliotheken nutzt smart pointer?



  • Hi,

    ich habe noch so meine Probleme im C++ Design. Immer wieder lese ich, dass zu modernen C++ RAII gehört. RAII wird in C++ ja bekanntermaßen mit smart pointer realisiert. Warum finde ich keine Bibliothek die smart pointer nutzt? Ich will mir den praktischen Umgang mit smart pointern aneignen. Die Theorie ist ja recht einfach, das Konzept habe ich, soweit ich das beurteilen kann, verstanden. Doch ich finde keine. Oder nutzen die im Geheimen smart pointer und man kann das im Interface nicht sehen?

    Derzeit versuche ich einen 3D Renderer bzw. eine Low-Level 3D Bibliothek zu schreiben. Nur kann ich mich zwischen C und C++ nicht entscheiden. Allgemein wäre C++ die bessere Wahl, C++ unterstützt direkt OOP (mache ich in C aber auch), hat wesentlich mehr Komfort und macht Speicherverwaltung mittels Destruktoren (oder smart pointer) einfacher. Doch ich habe Probleme mit dem Design, obwohl das eigentlich simpel ist.

    Das Problem lässt sich einfacher mit einem Beispiel beschreiben:

    class FrameBuffer;
    
    class GraphicsContext
    {
    private:
      FrameBuffer& backBuffer; //<- Problematisch, wenn backBuffer außerhalb freigegeben wird, da dann ungültig
    public:
      //...
      SetBackBuffer(FrameBuffer& buf);
    };
    

    Eine Lösung mit smart pointer wäre:

    class FrameBuffer;
    
    class GraphicsContext
    {
    private:
      std::shared_ptr<FrameBuffer> backBuffer;
    public:
      //...
      SetBackBuffer(std::shared_ptr<FrameBuffer> buf);
    };
    

    Finde ich aber irgendwie nicht so hübsch und habe ich auch noch nie so gesehen. Eine andere Lösung wäre noch im Destruktor von FrameBuffer dem Context mitzuteilen, dass der FrameBuffer gelöscht wird, damit müsste ich den FrameBuffer aber an einen einzigen Context binden. Ok könnte man das auch umgehen indem man eine lokale Liste anlegen würde, sowas lokales will ich aber vermeiden.

    Vielleicht habe ich aber einfach noch nicht das Gespür was gut ist. Was soll ich in dem Fall machen?

    Vielen Dank!



  • void_ptr schrieb:

    RAII wird in C++ ja bekanntermaßen mit smart pointer realisiert.

    Ne. RAII wird mit Konstruktoren und Destruktoren realisiert.

    Smart-Pointer sind nur eine Anwendung davon, für besitzende Zeiger. Genauso wie STL-Container eine Anwendung von RAII für Sequenzen von Elementen sind. Oder Locks eine Anwendung von RAII für Mutexes sind.

    void_ptr schrieb:

    Warum finde ich keine Bibliothek die smart pointer nutzt?

    C++11 und die damit erst richtig benutzbaren Smart-Pointer std::unique_ptr und std::shared_ptr sind relativ jung. Du findest sicher Bibliotheken, die sie bereits nutzen, aber eben nicht viele.

    Davon abgesehen sind viele C++-Bibliotheken einfach nur fragwürdig, was modernes Design angeht. Da würde ich mir nicht unbedingt zu viel abschauen.

    void_ptr schrieb:

    Was soll ich in dem Fall machen?

    Versuch immer zuerst, die Dinge einfach zu lösen. D.h. lege die Besitzverhältnisse so fest, dass keine ungültigen Referenzen oder Zeiger entstehen. Erst wenn du tatsächlich geteilten Besitz hast, ist shared_ptr angebracht. Und das ist sehr selten. Mache nicht den Fehler und verwende aus Bequemlichkeit shared_ptr -- du zahlst einen hohen Preis.



  • Ne. RAII wird mit Konstruktoren und Destruktoren realisiert.

    Achso sowas ist auch schon RAII. Macht Sinn.

    Im Fall von C hätte ich den Context selbst als Verwalter benutzt, wäre das auch in C++ sinnvoll? Oder wäre doch die Lösung mit dem Anbinden der Resource an einem Context besser? So hätte der Benutzer der Bibliothek mehr Freiheiten, könnte den Buffer selbst erstellen.



  • Ich hoffe, dass das verständlich ist. Es hört sich danach an, als wäre es das Gleiche.


  • Mod

    Was soll denn passieren, wenn backBuffer außerhalb freigegeben wird? Dies ist die Frage, die zu beantworten ist.

    Eine mögliche Technik wäre zum Beispiel etwas nach diesem Muster:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Ausgabedingens;
    
    class Sinnlos
    {
      Ausgabedingens *out;
    public:
      Sinnlos();
      void tu_was_sinnloses();
      Ausgabedingens* imbue(Ausgabedingens *neuer_wert)
      {
        Ausgabedingens *alter_wert  = out;
        out = neuer_wert;
        return alter_wert;
      }
    };
    
    class Ausgabedingens
    {
      string wert;
      Ausgabedingens *altes_dingens;
      Sinnlos &sinn;
    public:
      Ausgabedingens(string wert, Sinnlos &sinn): wert(wert), altes_dingens(sinn.imbue(this)), sinn(sinn) { }
      void write(string was) { cout << wert << was; }
      ~Ausgabedingens() {sinn.imbue(altes_dingens); }
    
      Ausgabedingens(Ausgabedingens&) = delete;
      void operator=(Ausgabedingens&) = delete;
    };
    
    void Sinnlos::tu_was_sinnloses() {if (out) out->write("Huhu.\n"); }
    Sinnlos::Sinnlos(): out(nullptr) {} 
    
    Sinnlos GlobalGraphicsContext;
    
    int main()
    {
      Ausgabedingens BackBuffer1("Ding 1: ", GlobalGraphicsContext);
      GlobalGraphicsContext.tu_was_sinnloses();
      {
        Ausgabedingens BackBuffer2("Ding 2: ", GlobalGraphicsContext);
        GlobalGraphicsContext.tu_was_sinnloses();
      }
      GlobalGraphicsContext.tu_was_sinnloses();
    }
    

    http://ideone.com/lsYmHK



  • Ah ich verstehe. Das ist genau das was ich mit Anbinden meinte, nur mit dem Unterschied, dass sogar der voherige backBuffer wieder "geladen" wird.

    Ich sehe schon, ich habe viel zu kompliziert gedacht. Vielen Dank!
    Aber wann benutzt man dann shared_ptr? Welcher Fall wäre dies?


  • Mod

    void_ptr schrieb:

    Aber wann benutzt man dann shared_ptr? Welcher Fall wäre dies?

    Nun, wie der Name schon sagt, wenn irgendwie die (besitzende) Pointerbeziehung geteilt wird. Das kommt zum Beispiel bei Multithreading vor oder wenn man Daten nach verschiedenen Kriterien indiziert werden. Wenn man sich noch nie mit diesen Dingen beschäftigt hat, dann bestehen auch gute Chancen, dass man bisher keine shared_ptr brauchte. (Und selbst wenn man sich damit beschäftigt hat, dann ist man dabei nicht zwangsläufig auf shared_ptr gestoßen.)

    Eine gelungen Erklärung der verschiedenen Smartpointer (Boost):
    http://stackoverflow.com/questions/569775/smart-pointers-boost-explained
    (Beitrag von Johannes Schaub)

    void_ptr schrieb:

    nur mit dem Unterschied, dass sogar der voherige backBuffer wieder "geladen" wird.

    Was genau passiert, kannst du natürlich machen, wie du möchtest. Daher auch meine Antwort, dass du erst einmal selber wissen musst, was in diesem Fall passieren soll.



  • Vielen Dank nochmal für die Erklärung. Dass ich shared_ptr verwenden wollte, stammte wohl aus meinen Anfängen mit C#, dort wird ja bei jedem Verweistypen referencecounting betrieben, shared_ptr ist ja quasi das Gleiche.



  • void_ptr schrieb:

    C#, dort wird ja bei jedem Verweistypen referencecounting betrieben

    Das war in den Anfängen so, mittlerweile sind Garbage Collectors um einiges weiter. Auf Wikipedia findest du eine gute Erklärung zu heutigen GC-Techniken.


Anmelden zum Antworten