Smart Pointer und Destruktoren



  • volkard schrieb:

    (Will schauen, ob es einen verantwortlichen Auto für diese Epidemie gibt.)

    Ja, mindestens einen gibt es: http://herbsutter.com/elements-of-modern-c-style/



  • manni66 schrieb:

    volkard schrieb:

    (Will schauen, ob es einen verantwortlichen Auto für diese Epidemie gibt.)

    Ja, mindestens einen gibt es: http://herbsutter.com/elements-of-modern-c-style/

    Ich habe mir in letzter Zeit tatsächlich einiges von Herb Sutter angeschaut. Den Eindruck den ich dabei bekommen habe ist, dass C++11 nicht mehr nur ein C mit viel Syntax Zucker ist, sondern dass es mehr "managed language like" wird. Ich wollte deshalb auch gleich mit C++11 einsteigen, jetzt sieht es aber eher so aus wie wenn das keine so gute Idee ist. Ein bestimmtes Buch habe ich keines, dafür aber wohl die falsche Erwartungshaltung.

    Wenn man sich in letzter Zeit so umschaut, dann wird einem überall gesagt, dass man kein new und delete mehr verwenden muss. Wenn man das eher verstehen muss im Sinne von "man muss kein new und delete mehr verwenden wenn man bereits C++ Profi ist", dann ist das ok, dann brauche ich von C++11 nämlich nichts erwarten was ich nicht bekommen kann.

    Soll ich lieber mit C ähnlichem Code anfangen, der C++ Header verwendet und mich dann nach oben hangeln und dabei C++11 Features möglichst außen vor lassen?



  • Wie hast du nur diesen Eindruck bekommen? Es ist sehr schwer jedes Werkzeug von C++ zu beherrschen. Es ist aber sehr einfach sich ein Problem zu nehmen und die passenden Werkzeuge (~5%) zu benutzen.
    Smartpointer sind einfach, eigentlich ist praktisch alles von C++11 auf Vereinfachung getrimmt. Nimm einfach shared_ptr in deinem Baum nachdem du verstanden hast was sie tun. Ist nicht so schwer, sie löschen die Resource nachdem der letzte shared_ptr weg ist. Damit kann man ganz leicht korrekte Programme schreiben.
    Später, wenn dir ein paar Nanosekunden zum Erfolg fehlen, dann kannst du rohe Pointer mit new und delete zu benutzen versuchen. Aber erst wenn du C++-Profi bist, davor nutze lieber den Komfort von C++11.



  • Antoras schrieb:

    Wenn man sich in letzter Zeit so umschaut, dann wird einem überall gesagt, dass man kein new und delete mehr verwenden muss.

    Ja, diesen Eindruck erweckt unser Forum. Hab's auch befürchtet, daß wir die Ursache sind und kein neues Buch.

    Vielleicht sollte man auch mal rohe Zeiger üben dürfen, geht aber nicht, verwendet man rohe Zeiger und fragt was, wird man sofort angebrüllt.

    Antoras schrieb:

    Soll ich lieber mit C ähnlichem Code anfangen, der C++ Header verwendet und mich dann nach oben hangeln und dabei C++11 Features möglichst außen vor lassen?

    Nee, macht wohl noch viel weniger Sinn. Aber woher wissen, was ein Luftschloss ist und was nicht? Naja, mehr als die übermäßige Verwendung von smart Pointers sehe ich zur Zeit nicht. Seltsamerweise werden die lamdas nicht völlig übertrieben. Weiß nicht, was ich allgemein empfehlen soll.

    Lass es vielleicht unter der Haube lockerer zugehen. Also innerhalb des Containers fühle Dich viel freier, den kannste ja gegen Bedienfehler abschließen.



  • Also, ich habe jetzt verschiedene Möglichkeiten ausprobiert, keine führte zum Erfolg.

    Zuerst habe ich alle pointer durch shared_ptr ersetzt, dabei bekam ich aber segfaults, da irgendwann mehrere shared_ptr auf den gleichen Speicherbereich gezeigt haben, das Problem habe ich aber schon im Ausgangspost angesprochen. Da ich keine Ahnung hatte wie ich das lösen sollte bin ich wieder auf normale Pointer umgestiegen, jetzt gebe ich aber immer zu wenig Speicher frei. Kleines Beispiel:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    enum AType {
      CT, DT, VT
    };
    
    class A {
    public:
    
      virtual ~A() {
        cout << "dtor: A(" << this << ")" << endl;
      }
    
      virtual AType typeOf() = 0;
    };
    
    class C : public A {
    private:
      A* a;
    
    public:
      C(A* a): a(a) {}
    
      ~C() {
        cout << "dtor: C(" << this << ")" << endl;
        delete a;
      }
    
      AType typeOf() { return CT; }
    
      A* value() { return a; }
    };
    
    class D : public A {
    private:
      A* a;
    
    public:
      D(A* a): a(a) {}
    
      ~D() {
        cout << "dtor: D(" << this << ")" << endl;
        delete a;
      }
    
      AType typeOf() { return DT; }
    
      A* value() { return a; }
    };
    
    class V : public A {
    private:
      int v;
    
    public:
      V(int v): v(v) {}
    
      ~V() {
        cout << "dtor: V(" << this << ")" << endl;
      }
    
      AType typeOf() { return VT; }
    
      int value() { return v; }
    };
    
    A* inner(A* a) {
      switch (a->typeOf()) {
        case CT:
          return ((C*) a)->value();
        case DT:
          return new D(((D*) a)->value());
        case VT:
         return a;
      }
    }
    
    int main() {
      shared_ptr<A> c(new C(new D(new V(5))));
      auto x = inner(c.get());
      auto y = inner(x);
    
      return 0;
    }
    

    Da ich es nicht hinbekommen habe mit Speicherreservierung in polymorphen Methoden umzugehen, habe ich die Methode inner() einfach ausgelagert (was zwar zu neuen Problemen geführt hat aber das ist mir jetzt erst mal egal).

    Man sieht in inner() meine benötigten Anwendungsfälle. Ich muss Speicher reservieren oder aber einen pointer auf einen bereits existierenden Bereich zurückgeben. Ich habe keine Ahnung wie ich den reservierten Speicher wieder freigeben soll. Wenn ich inner() nur mit shared_ptr arbeiten lasse habe ich dagegen wieder das Problem zu viel Speicher frei zu geben. Wie löse ich das Problem jetzt am besten?



  • Ich verstehe nicht was du eigentlich tun willst. Erkläre mal, warum class C sowohl ein A ist, als auch ein A hat.

    Ich fand deinen ersten Ansatz sehr viel besser als deinen zweiter und habe daran etwas rumgeschraubt. Besonders zu erwähnen ist Zeile 21, wo du manuell a gelöscht hast und später shared_ptr a gelöscht hat, was zu deinem Fehler führte. Weiterhin habe ich in Zeile 15 aus A einen shared_ptr gemacht. Ich verstehe den Sinn des Programms noch nicht, aber das sollte dich auf die richtigen Ideen bringen.

    #include <iostream> 
    #include <memory> 
    using namespace std;
    
    class A {
    public:
    	virtual ~A() {
    		cout << "dtor: A(" << this << ")" << endl;
    	}
    	virtual shared_ptr<A> inner() = 0;
    };
    
    class C : public A {
    private:
    	shared_ptr<A> a;
    public:
    	C(A* a) : a(a){
    	}
    	~C(){
    		cout << "dtor: C(" << this << ")" << endl;
    		//delete a; //hier war der Fehler! Du hast a gelöscht und der shared_ptr auch!
    	}
    	shared_ptr<A> inner() override{
    		return a;
    	}
    };
    
    class V : public A {
    private:
    	int v;
    public:
    	V(int v) : v(v) {}
    	~V() {
    		cout << "dtor: V(" << this << ")" << endl;
    	}
    	shared_ptr<A> inner() override{
    		return shared_ptr<A>(new V(v));
    	}
    };
    
    int main() {
    	shared_ptr<A> c(new C(new C(new V(5))));
    	auto inner = c->inner(); //nix mehr segfault 
    }
    


  • class A soll einen Baum symbolisieren, z.B. einen AST für mathematische Ausdrücke. inner() könnte eine simplify-Funktion sein, die z.B. 5*1 in 5 umwandelt.

    Dein Lösungsvorschlag hatte ich auch schon ausprobiert, er funktioniert aber nicht wenn du das 'a' aus Zeile 24 durch 'shared_ptr<A>(this);' ersetzt (das würde z.B. bei dem Ausdruck 5+1 eintreten, wenn dieser nicht weiter zu 6 vereinfacht werden soll).



  • Antoras schrieb:

    Dein Lösungsvorschlag hatte ich auch schon ausprobiert, er funktioniert aber nicht wenn du das 'a' aus Zeile 24 durch 'shared_ptr<A>(this);' ersetzt (das würde z.B. bei dem Ausdruck 5+1 eintreten, wenn dieser nicht weiter zu 6 vereinfacht werden soll).

    Naja, man darf halt nur Sachen dem shared_ptr geben, die der shared_ptr auch löschen soll. Aber ich verstehe das Problem.

    Eine mögliche Lösung ist eine Kopie zu machen:

    class C : public A {
    private:
        shared_ptr<A> a;
    public:
        C(A* a) : a(a){
        }
        explicit C(C &other) : a(other.a){
        }
        ~C(){
            cout << "dtor: C(" << this << ")" << endl;
            //delete a;
        }
        shared_ptr<A> inner() override{
            return C(*this); //Zeile 24 //nicht das * vergessen, sonst gibts segfaults!
        }
    };
    

    Es fühlt sich alles andere als optimal an.



  • Ich sollte auch schreiben was ich meine. Zeile 24 muss *return new C(this); heißen.



  • Komplett richtig ist wohl 'shared_ptr<A>(new C(*this));' 😉

    Das funktioniert jetzt endlich mal, danke!

    Ich habe mir schon gedacht, dass es irgendwie auf zwei Lösungen rausläuft: Entweder alle neu benötigten Teiles des Baums kopieren, oder aber sich irgendwie eine kleine mini Speicherverwaltungseinheit schreiben, die sich merkt was schon frei gegeben ist und was nicht und dann nur noch über diese Speicherverwaltungseinheit den Speicher freigeben lassen. Ersteres ist nicht effizient, aber für meine Zwecke vorerst ausreichend. Und letzteres ist mir zu aufwendig zu implementieren.

    Btw, wofür wird der Stern in 'new C(*this)' benötigt?



  • Der Stern dereferenziert den this Pointer. So wird der Kopierkonstruktor von C aufgerufen.



  • Weil Klasse C zwei Konstruktoren hat. Einer nimmt ein *A a, was ein neues C als Elternknoten von dem übergebenen a im Baum erzeugt, der andere nimmt ein C &c, was eine Kopie des Knoten machen soll. Das this ist ein *A **, also würde inner bei return new C(this) einen neuen Elternknoten von sich selbst erzeugen, während *this ein C ist (kein Zeiger) und eine Kopie erzeugt. Das sollte man unbedingt auseinander halten. Es sollte einen Syntaxfehler geben wenn man es falsch macht, wahrscheinlich sollte man statt eines Konstruktors lieber eine Memberfunktion copy oder so benutzen, damit man das Sternchen nicht vergessen kann.



  • evtl hilft dir auch eine fertige implementierung für kopierbare smart-pointer wie zb diese hier. mit shared_ptr würde ich nur dinge verwalten, die tatsächlich geteilt werden.



  • ok, danke nochmal für alle Antworten. Eine externe Lib werde ich jetzt noch nicht verwenden, ich schaue erst mal wie weit ich mit den std Bordmitteln komme.



  • shared_ptr hat in einem Graphen nichts zu suchen.



  • Kellerautomat schrieb:

    shared_ptr hat in einem Graphen nichts zu suchen.

    Es spricht nichts dagegen, in einem DAG shared_ptrs zu verwenden.


Anmelden zum Antworten