Kopierkonstruktor passt das so?



  • Wenn die Exception vom Programm nicht gefangen wird, beendet sich das Programm sowieso und dann spielen Speicherlecks auch keine Rolle mehr.



  • Mh, sagen wir mal, ich lege da zwei Objekte an die, die jeweils 1 MB groß sind. Außerdem unterstützt mein Betriebssystem kein Garbage Collection.
    Nun kriege ich für das 2. Objekt keinen Speicher und das erste liegt als Leiche im Speicher. 1MB verschwendet ... Weit hergeholt, weiß ich selber, ich bin lieber auf alle Eventualitäten vorbereitet, als das am Ende doch mal was schief geht.
    Bezogen auf den op=: wenn nun jemand die Ausnahme abfängt und dafür sorgt, dass das Programm ansonsten normal weiterläuft, habe ich als Ersteller dieser Klasse dafür Sorge zu tragen, dass ein Objekt nach so einem Fehlschlag konsistent bleibt. Bei dem Kopierkonstruktor sieht das natürlich anders aus, weil es ja vorher gar kein Objekt gab.



  • Nun, die Betriebssysteme, die ich kenne räumen den Speicher nach beendigung eines programmes schon auf.
    Beim op= ist das natürlich anders. Hier kann man aber schön das swap-Idiom von Herb sutter verwenden und hat dann auch keine Probleme mehr.



  • struct Foo {
         Foo() : a(0), b(0) {
              Megabyte* a = new Megabyte;
              Megabyte* b = new Megabyte;
         }
         ~Foo() {
              delete a; delete b;
         }
         Megabyte* a,b;
    };
    

    Das geht doch völlig in Ordnung. Wenn ein new schiefgeht können 2 Dinge passieren:
    - Die Exception wird nicht gefangen, dann ist eh alles egal - Ende Gelände.
    - Die Exception wird gefangen, dann werden auch die Objekte vom Stack genommen, der Destruktor übernimmt das Löschen der bereits angelegten Objekte.



  • und wenn du beim op= von Megabyte ne exception hast biste verratzt



  • Ich sehe da kein Problem...

    //in Foo
    Foo& operator=(Foo const& rhs) {
        delete a; delete b;
        a = b = 0;
        a = new Megabyte(*rhs.a);
        b = new Megabyte(*rhs.b);
    }
    

    hier speziell braucht man noch ein this != &rhs



  • Oh du hast von Megabyte geschrieben das hab ich gar net gesehn. Was hat der mit der ganzen Sache zu tun, den verwend ich nie...



  • Genau hier gibts ein Problem. Wenn das erste new im op= eine Exception wirft hast du ein uninitialisiertes Objekt. Mach es so

    struct Foo {
         Foo() : a(new Megabyte), b(new Megabyte) {}
         ~Foo() {
              delete a; delete b;
         }
         void Swap(Foo& src)
         {
            std::swap(a, src.a);
            std::swap(b, src.b);
         }
        Foo& operator=(Foo const& rhs) 
        {
           Foo tmp(rhs);
           Swap(tmp);
           return *this;
        }
         Megabyte* a,b;
    };
    


  • Oder nimm einfach shared_ptr aus boost oder tr1


  • Mod

    gesunder Menschenverstand schrieb:

    - Die Exception wird gefangen, dann werden auch die Objekte vom Stack genommen, der Destruktor übernimmt das Löschen der bereits angelegten Objekte.

    Keineswegs. Der Destruktor wird nur für lebende Objekte aufgerufen, das heißt, der Konstruktor muss normal beendet worden sein. Das Werfen einer Exception innerhalb des Konstruktors (und sei es in der Initialisierungsliste) verhindert das. Grundsätzlich ist das Problem ohne RAII für mehr als eine Resource, deren Anforderung scheitern kann, kaum zu bändigen. Möglich ist es, aber nicht empfelenswert:

    struct Foo
    {
        Foo()
        try
        : a( ( a = NULL, new Megabyte() ) ),
          b( new Megabyte() )
        {}
        catch (...)
        {
            delete a;
        }
        Foo(const Foo& other)
        try
        : a( ( a = NULL, new Megabyte( *other.a ) ) ),
          b( new Megabyte( *other.b ) )
        {}
        catch (...)
        {
            delete a;
        }
         ~Foo()
         {
              delete b;
              delete a;
         }
        Foo& operator=(Foo const& rhs)
        {
            Megabyte* new_a = new Megabyte( *rhs.a );
            try
            {
                Megabyte* new_b = new Megabyte( *rhs.b );
                delete b;
                delete a;
                a = new_a;
                b = new_b;
                return *this;
            }
            catch (...)
            {
                delete new_a;
                throw;
            }
        }
        Megabyte* a;
        Megabyte* b;
    };
    

    Bei Verwendung von scoped_ptr, auto_ptr oder ähnlichem wird das Ganze dagegen fast trivial.



  • Warum löscht du im try Block nur a? 😕
    Soweit ich weis ist es save auch nen null pointer zu löschen, ein delete b würde also nichts schaden..



  • Bei seiner Variante ist b kein Nullpointer. Warum sollte er b auch löschen? Wenn eine Exception fliegt ist b sowieso nicht initialisiert und wenn keine fliegt muß man auch nichts löschen.


Anmelden zum Antworten