Objekte löschen mit delete



  • Ja, ich programmiere unterm C++-Builder.

    Dass ich aus der C-Welt komme und gern mit Zeigern arbeite, heißt nur genau das, was es heißt. Da kann man schnell über ein paar Strukturzeiger riesige Datenmengen in wenigen Zeilen Code verwalten. Ich verzichte deswegen aber nicht auf "normale" Instanzen von Objekten und Variablen oder sonst was.

    Wenn mein Programmierstil so schlecht ist, hätte ich gern zwei Beispiele zum bessermachen:
    1. Eine Klasse soll eine ADOConnection enthalten.
    2. Diese Klasse soll ein weiteres Objekt enthalten, das diese ADOConnection als Konstruktor-Parameter bekommt.

    Aktuelle Lösung:

    class TMyClass
    {
      private:
        TADOConnection *ADOConnection;
        TMyChild *MyChild;
      public:
        TMyClass();
        ~TMyClass();
    }
    
    TMyClass::TMyClass()
    {
      this->ADOConnection = new TADOConnection(NULL);
      this->MyChild = new TMyChild(this->ADOConnection);
    }
    
    TMyClass::~TMyClass()
    {
      delete this->ADOConnection;
      delete this->MyChild;
    }
    


  • Wie siehts damit aus?

    class TMyClass
    {
      private:
        TADOConnection ADOConnection;
        TMyChild MyChild;
      public:
        TMyClass();
    }
    
    TMyClass::TMyClass() : ADOConnection(0), MyChild(&ADOConnection)
    {
    }
    


  • 12 vs 21 😉



  • erkenn0r schrieb:

    12 vs 21 😉

    sicher gegen unsicher.
    was passiert denn wenn new TMyChild im Originalcode fehlschlägt?



  • Heimelchen schrieb:

    Aktuelle Lösung:

    class TMyClass
    {
      private:
        TADOConnection *ADOConnection;
        TMyChild *MyChild;
      public:
        TMyClass();
        ~TMyClass();
    }
    
    TMyClass::TMyClass()
    {
      this->ADOConnection = new TADOConnection(NULL);
      this->MyChild = new TMyChild(this->ADOConnection);
    }
    
    TMyClass::~TMyClass()
    {
      delete this->ADOConnection;
      delete this->MyChild;
    }
    

    Diese "Lösung" ist nicht Ausnahme-sicher und missachtet auch die Dreier-Regel. Wenn der Konstruktor von TMyChild eine Ausnahme wirft, hast Du ein Speicherleck. Du hast außerdem vergessen, Kopierkonstruktor und Zuweisungsoperator "auszuschalten" oder selbst bereitzustellen. Denn so besteht die Gefahr der Doppel-Löschung. Diese Probleme hast Du nicht, wenn Du den eigentlich nacheliegenden Ansatz (siehe Biolunars Beitrag) verfolgst.

    Heimelchen schrieb:

    Dass ich aus der C-Welt komme und gern mit Zeigern arbeite, heißt nur genau das, was es heißt.

    Das heißt wahrscheinlich erstmal, dass Deine C++ Programme nicht nach "idiomatischen C++" aussehen werden.

    kk



  • Es gibt unter dem Borland Builder ein paar unschöne Features, z.B. kann man keine Objekte, die von TObject abgeleitet sind, auf dem Stack erzeugen sondern nur dynamisch auf dem Heap. Daher sind manchmal solche Handstände notwending, bevor ich boost benutzt habe musste ich auch solche Dinge machen.
    Am besten verpackst du die Objekte in einen smart Pointer wie scoped_ptr oder shared_ptr aus boost/TR1, damit brauchst du keinen Destruktor mehr, in dem du irgendwelche Objekte wegräumen musst.



  • Heimelchen schrieb:

    Aktuelle Lösung:

    Meine Frage nach dem C++ Builder kam nicht von ungefähr (siehe hierzu auch den Beitrag von DocShoe, auch wenn ich absichtlich nicht Borland in der Bezeichnung verwende - mit Borland hat der C++ Builder nichts mehr zu tun).

    In deinem Fall ist eine dynamische Speicherverwaltung (was Smartpointer aber immer noch nicht ausschließt) leider zum teil nötig (zumindest bei den von TObjekt abgeleiteten Klassen).

    //...
    #include <boost/scoped_ptr.hpp> // Einer der möglichen Smartpointer
    
    class TMyClass
    {
      private:
        boost::scoped_ptr<TADOConnection> adoConnection_;
        boost::scoped_ptr<TMyChild> myChild_;
    
        // Zur Vereinfachung unterbinde ich mal Kopie/Zuweisung.
        TMyClass(TMyClass const &);
        TMyClass& operator=(TMyClass const &);
    
      public:
        TMyClass();
    };
    
    TMyClass::TMyClass()
    :   adoConnection_(new TADOConnection(NULL)),
        myChild_(new TMyChild(adoConnection_))
    {
    }
    

    Heimelchen schrieb:

    Dass ich aus der C-Welt komme und gern mit Zeigern arbeite, heißt nur genau das, was es heißt.

    Du solltest dir unter C++ einen anderen Programmierstil angewöhnen. Die Programmierung ist unter beiden Sprachen unterschiedlich.



  • Biolunar schrieb:

    Wie siehts damit aus?

    class TMyClass
    {
      private:
        TADOConnection ADOConnection;
        TMyChild MyChild;
      public:
        TMyClass();
    }
    
    TMyClass::TMyClass() : ADOConnection(0), MyChild(&ADOConnection)
    {
    }
    

    Dann compilier mal:
    "Klassen im VCL-Stil müssen mit dem Operator new erstellt werden!" Ist ja nich so, dass ich nicht schon nen bissl probiert hätte...

    Da ich von C (auf Mikrocontrollern) komme, mag ich auch aufgeblähten Code nicht wirklich. Es ist ja eine Krankheit des "guten Programmierstils" oder "moderner Sprachen", dass Resourcen einfach verballert werden, um die mögliche Inkompetenz des Programmieres auszugleichen. Alle Welt regt sich über aufgeblasene Programme auf, aber auf Resourcen achten möchte auch keiner (überspitzt gesagt).

    Warum soll ich meine Anwendung mit boost aufblasen (tolles Wortspiel)? Nur damit ich keine Pointer verwenden muss?



  • Heimelchen schrieb:

    Dann compilier mal:
    "Klassen im VCL-Stil müssen mit dem Operator new erstellt werden!" Ist ja nich so, dass ich nicht schon nen bissl probiert hätte...

    Den Hinweis hast du uns nicht gegeben! (indirekt haben das Leute hier abgeleitet, wg. dem erwähnten "Form-Konstruktor").

    Warum soll ich meine Anwendung mit boost aufblasen (tolles Wortspiel)? Nur damit ich keine Pointer verwenden muss?

    boost::scoped_ptr (oder auto_ptr) sind sehr dünne Wrapper um den eigentlichen Pointer. Da wird nicht viel zusätzlicher Speicher verbraucht. Ein shared_ptr macht Reference Counting, das ist zur Laufzeit teurer.
    Der Sinn dahinter: Zerstört wird automatisch! RAII rocks! Denn was ich noch mehr hasse als Programme, die vllt. 0,5% mehr Speicher brauchen sind solche, die stetig wachsen und irgendwann den Rechner lahmlegen 😛 Memory Leak sucks!



  • Heimelchen schrieb:

    Da ich von C (auf Mikrocontrollern) komme, mag ich auch aufgeblähten Code nicht wirklich. Es ist ja eine Krankheit des "guten Programmierstils" oder "moderner Sprachen", dass Resourcen einfach verballert werden, um die mögliche Inkompetenz des Programmieres auszugleichen. Alle Welt regt sich über aufgeblasene Programme auf, aber auf Resourcen achten möchte auch keiner (überspitzt gesagt).

    Da scheinst du ja beim C++ Builder genau richtig gelandet zu sein. Alle Objekte dynamisch erzeugen zu müssen, um sie über irgendeine TObject Basisklasse verwenden zu können (oder was weiß ich wozu) scheint mir doch hier die Oberblähung zu sein. Das ständige Anfordern von Speicher über new ist ziemlcih aufwendig im vergelich zum Erzeugen in vorhandenem Speicher oder aufm Stack.
    Im Fall vom scoped_ptr würde ich mal fast behaupten, dass do gut wie der gleiche Code generiert wird. Da passiert ja überhaupt nichts anderes als delete aufzurufen. Blos muss man das nicht explizit hinschreiben.



  • Heimelchen schrieb:

    Warum soll ich meine Anwendung mit boost aufblasen (tolles Wortspiel)? Nur damit ich keine Pointer verwenden muss?

    Nein, um Fehlerquellen zu reduzieren, die du mit normalen Zeigern hast. Ebenso wie der Code häufig sogar sicherer und kürzer wird (In meinem Beispiel ist der Destruktor z.B. gänzlich unnötig).

    Ganz davon abgesehen das du - wenn du schon von aufblasen etc. sprichts, auch die Initialisierungsliste der nachträglichen Zuweisung im Konstruktor vorziehen solltest.



  • brotbernd schrieb:

    Da scheinst du ja beim C++ Builder genau richtig gelandet zu sein. Alle Objekte dynamisch erzeugen zu müssen...

    Alle: Nein. Seine "TMyClass" zeigt zumindest keine Aspekte warum diese per new alloziert werden müsste. Davon abgesehen das ich das "T" mit Sicherheit nicht vor eigenen Klassen schreiben würde, die nicht nach der VCL-Notation aufgebaut sind (Was auch den Vorteil hat das man ziemlich genau sieht, bei welchen Klassen die C++ Spielregeln teilweise nicht ganz gelten - wie Konstruktorreihenfolgen, new-Zwang...).



  • brotbernd schrieb:

    Normale Zeiger sollte man in gutem C++ Code möglichst selten sehen.

    Normale besitzende Zeiger. Zeiger, die lediglich passiv auf andere Objekte verweisen, sind kein Problem. Gleiches für Referenzen.



  • Um nochmal etwas produktiv zu werden:

    Ich habe eine Funktion, die einen TStrings* Parameter erwartet. Wie übergebe ich dieser einen std::auto_ptr<TStringList>? Kann ich den einfach auf TStringList* casten?


  • Administrator

    Heimelchen schrieb:

    Ich habe eine Funktion, die einen TStrings* Parameter erwartet. Wie übergebe ich dieser einen std::auto_ptr<TStringList>? Kann ich den einfach auf TStringList* casten?

    Ich nehme mal an, du meinstest, dass die Funktion als Parameter ein TStringList* erwartet? Dann schau in der Referenz von std::auto_ptr nach.

    Grüssli



  • TString und TStringList sagt mir nichts. Erklär mal, was Du machen willst.



  • void DoSomething(TStrings *Str);
    
    std::auto_ptr<TStringList>MyList(new TStringList(NULL);
    
    DoSomething(MyList.get());
    

    Das wollt ich tun.



  • Heimelchen schrieb:

    Ich habe eine Funktion, die einen TStrings* Parameter erwartet. Wie übergebe ich dieser einen std::auto_ptr<TStringList>? Kann ich den einfach auf TStringList* casten?

    std::auto_ptr<TStringList> sl;
    foo(sl.get());
    

  • Mod

    Heimelchen schrieb:

    Warum soll ich meine Anwendung mit boost aufblasen (tolles Wortspiel)? Nur damit ich keine Pointer verwenden muss?

    Die Prämisse ist fragwürdig. Für die Annahme, die Verwendung von boost-Elementen müsste notwendig die Applikation (mehr als ein handgeschriebenes Äquivalent) aufbähen, hat keine empirische Grundlage. Im Übrigen dienen Smartpointer nicht dazu, Pointer überflüssig zu machen, sondern den Umgang mit diesen zu vereinfachen.

    Heimelchen schrieb:

    Um nochmal etwas produktiv zu werden:

    Ich habe eine Funktion, die einen TStrings* Parameter erwartet. Wie übergebe ich dieser einen std::auto_ptr<TStringList>? Kann ich den einfach auf TStringList* casten?

    Das kommt darauf an. Übernimmt die Funktion den Besitz des TStrings, dann ist die Übergabe des Ergebnisses von p.release() angezeigt (p sei der auto_ptr), besser dann dürfte allerdings die Änderung der Funktion sein, damit diese von vornherein eine auto_ptr nimmt und damit die Semantik im Funktionstyp sichtbar ist.
    Andernfalls nimmst du einfach p.get()



  • Heimelchen schrieb:

    void DoSomething(TStrings *Str);
    
    std::auto_ptr<TStringList> MyList (new TStringList(NULL));
    
    DoSomething(MyList.get());
    

    Ohne zu wissen, was TStringList mit TStrings zu tun hat, kann ich Dir nicht helfen. Da es sich um keine Standard-Typen handelt, bist Du hier eher falsch.

    Bzgl auto_ptr, es gibt das source/sink-Muster:

    auto_ptr<foo> source();
    
    void peek(foo*) {}
    
    void sink2(foo* ptr)
    { auto_ptr<foo> ap (ptr); }
    
    void sink(auto_ptr<foo> ap)
    { sink2(ap.release()); }
    
    int main()
    {
      auto_ptr<foo> ap = source();
      peek(ap.get());
      sink(ap);
    }
    

    Hier wird das "Besitzverhältniss" (bzw die Verantwortlichkeit zum Löschen des foo-Objekts) von source an main, von main an sink und von sink an sink2 übergeben. Wenn Du eine Funktion aufrufen willst (peek), die diese Verantworltlichkeit nicht übernimmt, sollte sie auch keinen auto_ptr entgegen nehmen.

    std::auto_ptr ist aber mit Vorsicht zu genießen. Der bessere/sichere Ersatz ist std::unique_ptr, welchen es leider erst ab C++0x gibt.


Anmelden zum Antworten