4

Also haben wir einen Konstruktor, der abhängig von den übergebenen Argumenten eine Exception auslösen kann, aber wir wissen nicht, wie man das Objekt löscht, wenn dies passiert. Wichtiger Teil des Codes:Wie lösche Objekt, wenn Konstruktor eine Ausnahme auslöst?

try 
{ 
    GameBase *gameptr = GameBase::getGame(argc, argv); 
    if (gameptr == 0) 
    { 
     std::cout << "Correct usage: " << argv[PROGRAM_NAME] << " " << "TicTacToe" << std::endl; 
     return NO_GAME; 
    } 
    else 
    { 
     gameptr->play(); 
    } 
    delete gameptr; 
} 
catch (error e) 
{ 
    if (e == INVALID_DIMENSION) 
    { 
     std::cout << "Win condition is larger than the length of the board." << std::endl; 
     return e; 
    } 
} 
catch (...) 
{ 
    std::cout << "An exception was caught (probably bad_alloc from new operator)" << std::endl; 
    return GENERIC_ERROR; 
} 

In der dritten Zeile GameBase::getGame() ruft den Konstruktor für eines der Spiele, die von GameBase und gibt einen Zeiger auf dieses Spiel, und diese Konstrukteure können Ausnahmen werfen. Die Frage ist, wie können wir dann das (partielle?) Objekt löschen, auf das gameptr zeigt, wenn dies auftritt? Wenn eine Ausnahme ausgelöst wird, verlassen wir den Gültigkeitsbereich gameptr, weil wir den try-Block verlassen und delete gameptr nicht aufrufen können.

+4

Wenn der Konstruktor löst, wird das Objekt nicht konstruiert. Überhaupt nicht, also müssen Sie es nicht "löschen". Sie müssen sich nur sorgen, wenn Sie bereits Ressourcen zugewiesen haben, bevor die Ausnahme ausgelöst wird: Der Destruktor wird nicht ausgeführt. –

Antwort

8

Um die Ausnahmesicherheit zu beurteilen, müssen Sie weitere Einzelheiten zur Konstruktion des Objekts in GameBase::getGame angeben.

Die Regel ist durch, dass, wenn ein Konstruktor wirft, das Objekt nicht erstellt wird, daher wird der Destruktor nicht aufgerufen. Zugehörige Speicherzuordnungen werden ebenfalls freigegeben (d. H. Der Speicher für das Objekt selbst).

Das Problem wird dann, wie war der Speicher für den Anfang zugeordnet? Wenn es mit einer new GameBase(...) war, dann ist es nicht notwendig, den resultierenden Zeiger zu entziehen oder zu löschen - der Speicher wird von der Laufzeit freigegeben.


Für Klarheit darüber, was mit den Membervariablen geschieht, die bereits konstruiert sind; Sie werden auf der Ausnahme des "Eltern" -Objekts zerstört. Betrachten Sie die sample code;

Der Ausgang ist;

M ctor 
C ctor 
M dtor 
std::exception 

Wenn das M m_ Mitglied ist dynamisch zugewiesen werden, bevorzugen unique_ptr oder ein shared_ptr über einen nackten Zeiger und erlauben den intelligenten Zeiger das Objekt für Sie zu verwalten; wie folgt;

#include <iostream> 
#include <memory> 
using namespace std; 
struct M { 
    M() { cout << "M ctor" << endl; } 
    ~M() { cout << "M dtor" << endl; } 
}; 
struct C { 
    unique_ptr<M> m_; 
    C() : m_(new M()) { cout << "C ctor" << endl; throw exception(); } 
    ~C() { cout << "C dtor" << endl; } 
}; 

Die Ausgabe spiegelt den obigen Ausgang wider.

+0

Ja, in getGame() wird im Wesentlichen eine "neue GameBase()" von einem erfolgreichen Aufruf zurückgegeben. In GameBase() kann jedoch eine Ausnahme ausgelöst werden. Um klar zu sein, selbst wenn Instanzvariablen initialisiert werden, bevor die Exception im Konstruktor ausgelöst wird, sind sie (zusammen mit dem teilweise erstellten Objekt?) Immer noch zerstört, obwohl das Objekt auf dem Heap erstellt worden wäre? – Kerry

+2

Korrigieren. Alle Elementvariablen sind eigenständige Objekte und werden nach der Erstellung zerstört, wenn das übergeordnete Objekt eine Ausnahme auslöst. – Niall

+0

Wenn ich mich richtig erinnere, ist das nicht immer wahr, wenn Sie mehrere Objekte in einer Anweisung erstellen, weil der Zuordner und der Konstruktor verwechselt werden können. – hr0m

-1

Wenn Sie einen Konstruktor einwerfen, wird das Objekt nicht erstellt, und Sie sind daher für das Löschen zugewiesener Ressourcen verantwortlich. Das geht noch weiter! Betrachten Sie diesen Code

Es ist bis zu dem Compiler, in dem Reihenfolge ist die A zugeordnet und konstruiert. Sie könnten mit einem Speicherverlust enden, wenn Ihr A-Konstruktor werfen kann!

Edit: Verwenden statt

try{ 
auto first = std::make_unique<A>(); 
auto second = std::make_unique<A>(); 
int a = function(*first, *second); 
... 
+0

Also was ich denke ist, dass wenn ich Ressourcen zugewiesen Mit "new" im Konstruktor müsste ich sie löschen (in einem catch-Block im Konstruktor)? Was passiert, wenn die Instanzvariablen nicht mit neuen, sondern stack-Variablen zugewiesen werden? – Kerry

+0

Stack-Variablen sind in Ordnung. Mitgliedsvariablen sollten auch in Ordnung sein. Sie müssen nur den Konstruktor abfangen und neu zuordnen und dann erneut starten, damit auch der obere Code reagieren kann. Sie können den Code in fstream oder eine Klasse, die das RAII-Prinzip verwendet, suchen. – hr0m

+0

@ K.Li das Problem hier ist, dass die Implementierung wählen konnte, Speicher für beide 'A' zuweisen, dann rufen Sie einen der Konstruktoren. Wenn dies ausgelöst wird, wird der Speicher für das "A", dessen Konstruktor geworfen (warf?), Zurückgewonnen, aber der andere nicht. – TartanLlama

3

Wenn Sie Foo* result = new Foo() schreiben, übersetzt der Compiler dies dem Äquivalent von diesem Code:

void* temp = operator new(sizeof(Foo)); // allocate raw memory 
try { 
    Foo* temp2 = new (temp) Foo(); // call constructor 
    result = temp2; 
} catch (...) { 
    operator delete(temp); // constructor threw, deallocate memory 
    throw; 
} 

Sie müssen also nicht über die zugewiesenen Sorge Speicher, wenn der Konstruktor wirft. Beachten Sie jedoch, dass dies nicht für zusätzlichen Speicher gilt, der im Konstruktor zugewiesen wurde.Destruktoren werden nur für Objekte aufgerufen, deren Konstruktor abgeschlossen ist. Daher sollten Sie alle Zuordnungen sofort in kleine Wrapper-Objekte (Smart Pointer) übernehmen.

+0

Und wenn Sie Zuweisungen sagen, meinen Sie Speicher zugeordnet mit 'new', richtig? – Kerry

+0

Eigentlich meine ich irgendeine Ressourcenzuweisung überhaupt. Speicher zugewiesen mit 'new', mit' malloc', mit 'VirtualAlloc', Dateihandles, Sockets, Datenbankverbindungen, alles. Es ist nur eine Neuformulierung des RAII-Prinzips. –

Verwandte Themen