2008-09-29 22 views
18
class someclass {}; 

class base 
{ 
    int a; 
    int *pint; 
    someclass objsomeclass; 
    someclass* psomeclass; 
public: 
    base() 
    { 
     objsomeclass = someclass(); 
     psomeclass = new someclass(); 
     pint = new int(); 
     throw "constructor failed"; 
     a = 43; 
    } 
} 

int main() 
{ 
    base temp(); 
} 

In dem obigen Code löst der Konstruktor. Welche Objekte werden geleakt und wie können Speicherlecks vermieden werden?Verursacht der folgende Code Speicherverlust in C++

int main() 
{ 
    base *temp = new base(); 
} 

Wie wäre es mit dem obigen Code? Wie können Speicherlecks vermieden werden, nachdem der Konstruktor geworfen hat?

+1

Ich weiß, ich habe eine schreckliche Natur, die ich gegen Nitpick nicht widerstehen kann. Ich kann nicht anders. Meine 2 Cent: Anweisung objsomeclass = someclass(); ist unnötig. Im Rumpf des Konstruktors ist objsomeclass bereits standardmäßig initialisiert. objsomeclass (someclass()) unten macht auch keinen Sinn. –

+0

Ich stimme zu, aber denke, dass eine Klasse einen expliziten Konstruktor hat. Und ich wollte auf das Objekt konzentrieren wird im Konstruktor erstellt – yesraaj

+0

Ja, ich weiß, es ist nur ein Beispiel. Deshalb nannte ich es pingelig. BTW Konstruktor base() könnte öffentlich sein :) –

Antwort

35

Ja, es wird Speicherleck. Wenn der Konstruktor ausgibt, wird kein Destruktor aufgerufen (in diesem Fall wird kein Destruktor angezeigt, der die dynamisch zugewiesenen Objekte freigibt, aber wir nehmen an, dass Sie einen hatten).

Dies ist ein wichtiger Grund für die Verwendung von Smartpointern - da die Smart poitners vollwertige Objekte sind, erhalten sie Destruktoren, die während des Stackabwickels der Exception aufgerufen werden, und haben die Möglichkeit, den Speicher freizugeben.

Wenn Sie scoped_ptr verwenden ist so etwas wie Boost-<> Vorlage, könnte Ihre Klasse aussehen wie:

class base{ 
    int a; 
    scoped_ptr<int> pint; 
    someclass objsomeclass; 
    scoped_ptr<someclass> psomeclass; 
    base() : 
     pint(new int), 
     objsomeclass(someclass()), 
     psomeclass(new someclass()) 

    { 
     throw "constructor failed"; 
     a = 43; 
    } 
} 

Und Sie würden keine Speicherlecks haben (und der Standard-dtor würde auch die dynamischen Speicherzuordnungen aufzuzuräumen) .


Zusammengefasst (und hoffentlich beantwortet auch die Frage nach der

base* temp = new base(); 

Anweisung):

Wenn eine Ausnahme in einem Konstruktor geworfen wird, gibt es mehrere Dinge, die Sie ergreifen sollten Hinweis bezüglich der korrekten Handhabung von Ressourcenzuordnungen, die bei der abgebrochenen Konstruktion des Objekts aufgetreten sein könnten:

  1. der Destruktor für das zu erstellende Objekt wird nicht genannt werden.
  2. Destruktoren für Member-Objekte in der Klasse dieses Objekts werden
  3. der Speicher für das Objekt, das erstellt wurde, wird freigegeben werden.

Dies bedeutet, dass, wenn Ihr Objekt Ressourcen besitzt, haben Sie zwei Methoden, diese Ressourcen zu bereinigen, zur Verfügung, die bereits erworben haben könnte, wenn der Konstruktor wirft:

  1. fängt die Ausnahme, die Ressourcen freigeben, dann erneut ausführen. Dies kann schwierig sein, richtig zu werden und kann zu einem Wartungsproblem werden.
  2. Verwenden Sie Objekte zum Verwalten der Ressourcenlebensdauern (RAII) und verwenden Sie diese Objekte als Mitglieder. Wenn der Konstruktor für Ihr Objekt eine Ausnahme auslöst, werden die Memberobjekte Destruktoren aufgerufen und haben die Möglichkeit, die Ressource freizugeben, für deren Lebensdauer sie verantwortlich sind.
+0

Ist nicht in Boost schleppen gehen Speicherverwaltung ziemlich dumm? –

+0

Vielleicht, aber scoped_ptr ist in TR1 und wird in C++ 09 sein, also ist es etwas, das sowieso gelernt werden sollte. Und der Teil von Boost, der scoped_ptr hat, ist nur ein Haufen Header. Schließlich könnten Sie stattdessen auto_ptr für dieses einfache Beispiel verwenden, aber auto_ptr ist wahrscheinlich etwas, das vermieden werden sollte. –

+0

wird der dtor der Basisklasse aufgerufen, auch wenn ich einmal habe? Was passiert mit der unteren Zeile? base * temp = new base(); – yesraaj

-2

Alles, was Sie "neu" müssen gelöscht werden, oder Sie werden ein Speicherleck verursachen. Also diese beiden Linien:

psomeclass = new someclass(); 
pint = new int(); 

Werden Speicherlecks verursachen, weil Sie tun müssen:

delete pint; 
delete psomeclass; 

In einem finally-Block zu vermeiden, dass sie durchgesickert.

Auch diese Zeile:

base temp = base(); 

nicht notwendig ist. Sie müssen nur Folgendes tun:

Das Hinzufügen der "= base()" ist nicht erforderlich.

+1

Kein "finally" Block in C++ –

+0

Wahr, Sie können oder dürfen nicht Zugriff auf es abhängig von Ihrem C++ - Geschmack haben - wenn nicht müssen Sie sicherstellen, dass die Zuweisungen gelöscht werden, unabhängig vom Codepfad. – Colen

+1

Ihre Bemerkung über die zusätzliche Initialisierung ist falsch. Das resultierende Objekt wird nur einmal initialisiert und nicht kopiert. –

0

Ja, dieser Code wird Speicher verlieren. Mit "new" zugewiesene Speicherblöcke werden nicht freigegeben, wenn eine Ausnahme ausgelöst wird. Dies ist Teil der Motivation hinter RAII.

Um den Speicherverlust, versuchen Sie so etwas wie dies zu vermeiden:

psomeclass = NULL; 
pint = NULL; 
/* So on for any pointers you allocate */ 

try { 
    objsomeclass = someclass(); 
    psomeclass = new someclass(); 
    pint = new int(); 
    throw "constructor failed"; 
    a = 43; 
} 
catch (...) 
{ 
    delete psomeclass; 
    delete pint; 
    throw; 
} 

+0

anstatt die Verwendung von Zeigern mit Objekt (Smart-Pointer) wird die Dinge besser machen Da seit wann eine Ausnahme in einem Block ausgelöst wird, werden automatische Objekte gelöscht. – yesraaj

+0

intelligente Zeiger sind besser. Ersetzen Sie auch 'erhöhen'; mit "werfen" Um die aktuelle Ausnahme erneut auszulösen. –

0

Wenn Sie in einem Konstruktor werfen, sollten Sie alles aufzuräumen, dass der Anruf kam vor zu werfen. Wenn Sie Vererbung verwenden oder einen Destruktor einwerfen, sollten Sie es wirklich nicht sein. Das Verhalten ist seltsam (habe nicht mein Standardhandy, aber es könnte undefiniert sein?).

+0

Nicht sicher, ob es tatsächlich undefiniert ist, aber es ist sicherlich sehr gefährlich, da Destruktoren während des Stackabwickelns bei einer ausgelösten Ausnahme aufgerufen werden. Wenn Sie eine Ausnahme auslösen, während eine andere ausgelöst wurde, wird jede C++ - Laufzeit, die ich kenne, die Anwendung beenden. –

+0

Eine nicht abgefangene Ausnahme in einem Destruktor, der während der Ausnahmebehandlung ausgelöst wird, verursacht den Aufruf von std :: terminate(), der standardmäßig std :: abort() aufruft. Das Standardverhalten kann überschrieben werden. – KTC

+0

Auch wenn das Standardverhalten überschrieben werden kann, kann Ihre Version immer noch nicht zur Anwendung zurückkehren, sie muss dennoch beendet werden. –

5

Beide neuen werden durchgesickert.

Weisen Sie die Adresse der Halde erstellten Objekte zu namens intelligente Zeiger, so dass es in der Smart-Pointer destructor gelöscht werden, rufen Sie erhalten, wenn die Ausnahme ausgelöst wird - (RAII).

class base { 
    int a; 
    boost::shared_ptr<int> pint; 
    someclass objsomeclass; 
    boost::shared_ptr<someclass> psomeclass; 

    base() : 
     objsomeclass(someclass()), 
     boost::shared_ptr<someclass> psomeclass(new someclass()), 
     boost::shared_ptr<int> pint(new int()) 
    { 
     throw "constructor failed"; 
     a = 43; 
    } 
}; 

Jetzt psomeclass & Pint Destruktoren werden aufgerufen, wenn der Stapel entspannen, wenn die Ausnahme im Konstruktor geworfen wird, und die Destruktoren den zugewiesenen Speicher freigeben.

int main(){ 
    base *temp = new base(); 
} 

Für gewöhnliche Speicherzuordnung unter Verwendung von (nicht-plcaement) neu, wird neu von der Bedienungsperson zugewiesenen Speicher automatisch freigegeben, wenn der Konstruktor eine Ausnahme ausgelöst. Was die Frage betrifft, warum einzelne Mitglieder freigestellt werden (als Antwort auf die Antwort auf Mike Bs Antwort), gilt die automatische Freigabe nur, wenn eine Ausnahme in einem Konstruktor eines neu zugewiesenen Objekts ausgelöst wird, in anderen Fällen nicht. Außerdem ist der freigegebene Speicher derjenige, der den Objektmembern zugewiesen wurde, und nicht der Speicher, den Sie im Konstruktor zugewiesen haben. d.h.Es wäre der Speicher für die Membervariablen frei einem, Pint, objsomeclass und psomeclass, aber nicht der Speicher von neuen Someclass() und new int() zugeordnet.

+0

shared_ptr <> ist Overkill, wenn Sie das Objekt besitzen und niemals eine gemeinsame Eigentümerschaft geben.Vereinfachen Sie mit std :: auto_ptr <> –

+0

// änderte die Frage, um zu haben base * temp = neue base(); – yesraaj

+0

Und boost :: scoped_ptr <> könnte sogar besser sein als auto_ptr <>, das seine eigene Dose Würmer hat. –

-3

benötigen Sie psomeclass ... Es ist nicht notwendig, um aufzuräumen die ganze Zahl zu löschen ...

RWendi

+0

Können Sie bitte Dave Moore ausarbeiten? Geht es um den Teil "nicht notwendig um den Integer aufzuräumen"? Der Grund dafür ist, dass der Int-Speicherzeiger im Vergleich zum Klassenspeicherzeiger nicht viel kostet, weshalb ich sagte, dass es nicht notwendig ist, ihn zu bereinigen. – RWendi

+0

Sie beide lecken; Kosten sind kein Problem. Die Frage war, ob es durchgesickert ist oder nicht. Und wenn dieser Codeabschnitt Tausende oder Millionen Male ausgeführt wird, summieren sich diese kleinen Kosten. Auch wenn "Kosten" relevant waren, ist es nicht die Größe des * Zeigers *, die einen Unterschied machen, sondern eher die Größe der angezeigten Entität. Zum Beispiel ist es möglich für sizeof (someclass) == sizeof (int). Und du löschst nicht den Zeiger - du löschst das Zeigeelement. –

1

Ich glaube, dass die Top-Antwort falsch ist und nach wie vor ein Speicherleck würde. Der Destruktor für die Klassenmitglieder wird nicht aufgerufen werden, wenn der Konstruktor eine Ausnahme auslöst (weil es nie seine Initialisierung abgeschlossen hat und vielleicht einige Mitglieder ihre Konstruktoraufrufe nie erreicht haben). Ihre Destruktoren werden nur während des Destruktoraufrufs der Klasse aufgerufen. Das macht nur Sinn.

Dieses einfache Programm demonstriert es.

#include <stdio.h> 


class A 
{ 
    int x; 

public: 
    A(int x) : x(x) { printf("A constructor [%d]\n", x); } 
    ~A() { printf("A destructor [%d]\n", x); } 
}; 


class B 
{ 
    A a1; 
    A a2; 

public: 
    B() 
    : a1(3), 
     a2(5) 
    { 
     printf("B constructor\n"); 
     throw "failed"; 
    } 
    ~B() { printf("B destructor\n"); } 
}; 


int main() 
{ 
    B b; 

    return 0; 
} 

Mit der folgenden Ausgabe (mit g ++ 4.5.2):

A constructor [3] 
A constructor [5] 
B constructor 
terminate called after throwing an instance of 'char const*' 
Aborted 

Wenn Ihr Konstruktor partway dann scheitert es liegt in Ihrer Verantwortung, damit umzugehen. Schlimmer noch, die Ausnahme kann vom Konstruktor der Basisklasse ausgelöst werden! Der Weg, um mit diesen Fällen umzugehen, besteht darin, einen "Funktionsversuchsblock" zu verwenden (aber selbst dann müssen Sie die Zerstörung Ihres teilweise initialisierten Objekts sorgfältig codieren).

Der richtige Ansatz, um Ihr Problem so etwas wie dieses wäre dann:

#include <stdio.h> 


class A 
{ 
    int x; 

public: 
    A(int x) : x(x) { printf("A constructor [%d]\n", x); } 
    ~A() { printf("A destructor [%d]\n", x); } 
}; 


class B 
{ 
    A * a1; 
    A * a2; 

public: 
    B() 
    try // <--- Notice this change 
    : a1(NULL), 
     a2(NULL) 
    { 
     printf("B constructor\n"); 
     a1 = new A(3); 
     throw "fail"; 
     a2 = new A(5); 
    } 
    catch (...) { // <--- Notice this change 
     printf("B Cleanup\n"); 
     delete a2; // It's ok if it's NULL. 
     delete a1; // It's ok if it's NULL. 
    } 

    ~B() { printf("B destructor\n"); } 
}; 


int main() 
{ 
    B b; 

    return 0; 
} 

Wenn Sie es laufen Sie die erwartete Ausgabe, wo nur die zugeordneten Objekte werden zerstört und befreit.

Sie können es immer noch mit intelligenten freigegebenen Zeigern arbeiten, wenn Sie möchten, mit zusätzlichen Kopieren. Schreiben Sie einen Konstruktor wie folgt aus:

class C 
{ 
    std::shared_ptr<someclass> a1; 
    std::shared_ptr<someclass> a2; 

public: 
    C() 
    { 
     std::shared_ptr<someclass> new_a1(new someclass()); 
     std::shared_ptr<someclass> new_a2(new someclass()); 

     // You will reach here only if both allocations succeeded. Exception will free them both since they were allocated as automatic variables on the stack. 
     a1 = new_a1; 
     a2 = new_a2; 
    } 
} 

Viel Glück, Tzvi.

+0

Die Ausnahme in Ihrem ersten Beispiel wird nicht abgefangen, daher kommt es nicht zu einem Stackabwickeln, und es werden keine Destruktoren aufgerufen. Wenn Sie 'B b;' in einen try catch einbinden, werden die Destruktoren wie erwartet aufgerufen. –