2009-04-10 5 views
17

Was ist die beste Technik zum Beenden von einem Konstruktor auf eine Fehlerbedingung in C++? Dies ist insbesondere ein Fehler beim Öffnen einer Datei.Was ist die beste Technik zum Beenden eines Konstruktors auf eine Fehlerbedingung in C++

Danke für die Antworten. Ich werfe eine Ausnahme. Hier ist der Code (weiß nicht, ob es die beste Art und Weise, es zu tun, aber es ist einfach)

// Test to see if file is now open; die otherwise 
if (!file.is_open()) { 
    cerr << "Failed to open file: " << m_filename << endl; 
    throw ("Failed to open file"); 
} 

One Ich denke über C wie ++ ist, Sie müssen nicht geworfen Ausnahmen auf die Methode Erklärungen erklären .

+0

Ich würde empfehlen, eine std :: runtime_error, oder mindestens eine std :: Ausnahme anstelle einer const char * zu werfen. – GManNickG

+0

mögliche Duplikate von [Wie Fehler in Konstruktor in C++ behandelt werden?] (Http://stackoverflow.com/questions/4989807/how-to-handle-failure-in-constructor-in-c) –

Antwort

25

Der beste Vorschlag ist wahrscheinlich was parashift sagt. Aber lies bitte auch meinen Warnhinweis unten.

See parashift FAQ 17.2

[17.2] Wie kann ich behandeln einen Konstruktor , die fehlschlägt?

Eine Ausnahme auslösen.

Konstruktoren haben keinen Rückgabetyp, , daher ist es nicht möglich, Codes zu verwenden. Der beste Weg, um Konstruktorfehler zu signalisieren, ist daher eine Ausnahme zu werfen. Wenn Sie die Option der Verwendung von Ausnahmen nicht haben, "am wenigsten schlecht" Workaround ist das Objekt in einen "Zombie" -Zustand von setzen ein internes Status-Bit, so dass das Objekt verhält sich wie es ist tot obwohl es technisch immer noch am Leben ist.

Die Idee eines "Zombie" -Objekts hat eine Menge von Down-Side. Sie müssen Anfrage ("Inspektor") Mitglied Funktion zu überprüfen Sie diese "Zombie" Bit, so dass Benutzer von Ihre Klasse kann herausfinden, ob ihre Objekt wirklich lebendig ist, oder wenn es ein Zombie ist (dh ein " totes "Objekt", und so ziemlich jeden Ort, den Sie eines Ihrer Objekte Konstruieren (einschließlich in einem größeren Objekt oder ein Array von Objekten) müssen Sie überprüfen, das Status-Flag über eine if-Anweisung. Sie möchten auch ein if zu Ihrem anderen Mitglied Funktionen hinzufügen: Wenn das Objekt ein Zombie ist, machen Sie einen No-Op oder vielleicht etwas anstößiger.

In der Praxis bekommt das "Zombie" Ding ziemlich hässlich. Natürlich sollten Sie bevorzugen Ausnahmen über Zombie-Objekte, aber wenn Sie nicht die Option mit Ausnahmen haben, könnten Zombie-Objekte die "am wenigsten schlechte" Alternative sein.


Ein Wort der Vorsicht mit Ausnahmen in einem Konstruktor werfen:

sehr vorsichtig sein, aber, weil, wenn eine Ausnahme in einem Konstruktor geworfen wird, die Klasse des Destruktor wird nicht aufgerufen. Sie müssen daher vorsichtig sein, Objekte zu zerstören, die Sie bereits erstellt haben, bevor die Ausnahme ausgelöst wird.Die gleichen Warnungen gelten im Allgemeinen für die Ausnahmebehandlung, aber im Umgang mit einem Konstruktor ist es vielleicht etwas weniger offensichtlich.

class B 
{ 
public: 
    B() 
    { 

    } 

    virtual ~B() 
    { 
     //called after D's constructor's exception is called 
    } 
}; 

class D : public B 
{ 
public: 
    D() 
    { 
     p = new char[1024]; 
     throw std::exception("test"); 
    } 

    ~D() 
    { 
     delete[] p; 
     //never called, so p causes a memory leak 
    } 

    char *p; 
}; 

int main(int argc, char **argv) 
{ 

    B *p; 
    try 
    { 
     p = new D(); 
    } 
    catch(...) 
    { 

    } 


    return 0; 
} 

Geschützte/Privat Konstrukteuren mit CreateInstance Methode:

Ein anderer Weg, um dieses ist Ihr Konstruktor privat oder geschützt zu machen und eine CreateInstance Methode machen, die Fehler zurückgeben kann.

+0

@Neil Butterworth: I Ich versuche mich zu erinnern, warum, ich hatte Probleme damit, vorher mit einem Patch zur CLucene-Bibliothek, etwas mit abgeleiteten Typen. Es führte irgendwie zu einem teilweise konstruierten Typ, der einen reinen virtuellen Funktionsaufruf verursachte, um das Programm unter Verwendung von CLucene zum Absturz zu bringen. –

+0

Aktualisiert mit dem Grund, warum, kann es zu einer Menge von Problemen führen, wenn Ihr Konstruktor nicht nach sich aufräumt, bevor die Ausnahme aufgerufen wird. –

+0

Entschuldigung - ich denke, sie gelten alle gleichermaßen für die Verwendung einer Rückgabe von einem Konstruktor, wenn der Destruktor auf einem wahrscheinlich falsch konstruierten Objekt aufgerufen wird, mit schrecklichen Ergebnissen. In beiden Fällen besteht die Antwort darin, selbstverwaltende Objekte als Klassenmitglieder zu verwenden. –

1

Wenn Sie ablehnen, nachdem der Fehler seine Aktionen nicht ausführen kann - müssen Sie werfen. Wenn es kann - log dich Fehler und ändere die Konstruktionslogik.

4

Im Allgemeinen sollten Sie eine Ausnahme auslösen. Die Alternative besteht darin, ein halb korrekt konstruiertes Objekt zu haben, das der Benutzer irgendwie testen muss, was aber unvermeidlich fehlschlägt.

2

Wenn das Objekt, das Sie konstruieren, aufgrund des Fehlers ungültig ist und vom Aufrufer entfernt werden muss, müssen Sie ziemlich genau eine Ausnahme auslösen. Dies ermöglicht dem Compiler, die richtige Freigabe von Ressourcen durchzuführen.

(Das Schreiben von ausnahmesicheren Konstruktoren erfordert ein wenig Sorgfalt - kurz gesagt, Sie müssen die Initialisierungslisten verwenden, wo immer Sie können, anstatt den Konstruktorkörper zu verwenden - aber es ist kritisch, wenn Sie einen solchen Fall haben, wo das Werfen einer Ausnahme eine bedeutende Möglichkeit ist.)

+0

Können Sie etwas näher erläutern, warum es wichtig ist, Initialisierungslisten zu verwenden, wenn eine Ausnahme ausgelöst wird? Sie scheinen zu implizieren, dass es inhärent nicht ausnahmslos sicher wäre, aus dem Konstruktor Körper zu werfen, aber ich bin mir nicht sicher, ob ich sehe warum. –

+0

Ich meinte nicht, dass ein Wurf vom Körper unsicher ist. Was ich meinte, ist, dass wenn man von irgendwo in den ctor (Körper oder Initialisierungslisten) wirft, die Dktoren für alle Felder, die initialisiert wurden, aufgerufen werden. Angenommen, der Wurf findet während eines ctor für ein Klassenmitglied statt, das selbst eine Klasseninstanz ist. –

+0

(cnt'd) - der rufende ctor muss alle initialisierten Felder zerstören und muss * nicht * wissen, um Felder zu löschen, die noch nicht initialisiert wurden.Der Compiler stellt sicher, dass dies korrekt geschieht. Es ist unmöglich, es in allen Fällen richtig zu machen, wenn Sie den Compiler umgehen und es selbst tun. –

0

Es gibt nur eine gute Weise, von einem Konstruktor zu beenden, der ein Fehler ist, dh eine Ausnahme auszulösen.

Ist das wirklich ein Fehler? Versuchen Sie, dem Konstruktor zu viel hinzuzufügen?

Oft versuchen Leute, eine erste Interaktion in den Konstruktor einzufügen, wie das Hinzufügen des Dateinamens zu einem Dateikonstruktor. Erwarten Sie, dass diese Datei sofort geöffnet wird oder Sie nur einen Zustand einstellen, ist das anders als file.open (Dateiname), ist es in Ordnung, wenn es fehlschlägt?

6

Sie können eine Ausnahme auslösen, wie andere bereits erwähnt haben, oder Sie können Ihren Code auch umgestalten, damit Ihr Konstruktor nicht fehlschlagen kann. Wenn Sie beispielsweise an einem Projekt arbeiten, für das Ausnahmen deaktiviert oder nicht zugelassen sind, ist Letzteres die beste Option.

Um einen Konstruktor zu machen, die nicht fehlschlagen kann, den Code Refactoring, die möglicherweise in ein init() Verfahren scheitern könnte, und haben den Konstruktor tun so wenig Arbeit wie möglich und erfordern dann alle Benutzer der Klasse init() sofort anrufen nach Konstruktion. Wenn init() fehlschlägt, können Sie einen Fehlercode zurückgeben. Stellen Sie sicher, dies in der Dokumentation Ihrer Klasse zu dokumentieren!

Natürlich ist das etwas gefährlich, da Programmierer vergessen könnten, init() zu rufen. Der Compiler kann dies nicht erzwingen, gehen Sie also vorsichtig vor und versuchen Sie, Ihren Code fehlzuschlagen, wenn init() nicht aufgerufen wird.

+0

Ein Konstruktor kann immer fehlschlagen. Alles, was Speicher reservieren muss, kann immer fehlschlagen, besonders in der Art von Umgebung, die wahrscheinlich keine Ausnahmen verwendet. –

+0

Ich würde gerne wissen, wie ein Konstruktor, der nichts tut, oder eine, die nur POD initialisiert, fehlschlagen kann. –

+0

Es kann bei der Speicherzuweisung fehlschlagen. new weist Speicher zu und initialisiert es. Wenn Sie den Arbeitsspeicher vorab reservieren und die Platzierung neu verwenden, können Sie einen Konstruktor erhalten, der nicht geworfen werden kann. –

0

Das beste, was zu tun ist, ist eine Ausnahme zu werfen. Dafür sind sie da, und jeder Versuch, das Verhalten, das Sie bekommen, zu duplizieren, wird wahrscheinlich irgendwo fehlschlagen.

Wenn Sie keine Ausnahme verwenden können, verwenden Sie aus irgendeinem Grund nothrow. Das Beispiel im Standard, 18.4.1.1 Satz 9 ist:

t* p2 = new(nothrow) T; // returns 0 if it fails 

Dies ist technisch eine Form der Platzierung neu, aber es sollte ein vollständig gebildetes Objekt oder einen Null-Zeiger, den Sie testen müssen für, mit der Ausnahme, dass niemand entweder zurück.

Wenn Ihre Klasse ein Objekt haben kann, das dort aber nicht ordnungsgemäß initialisiert ist, können Sie ein Datenelement haben, das als Flag dient, ob die Klasse nützlich ist oder nicht. Auch hier wird niemand diese Flagge im Live-Code überprüfen.

Denken Sie daran, dass Sie, wenn Sie wirklich eine Zuteilung haben müssen, die garantiert nicht scheitern muss, den Speicher vorzeitig zuordnen und die neue Platzierung verwenden und alle Initialisierungen entfernen müssen, die eine andere Routine auslösen könnten jemand wird nicht anrufen. Alles, was Speicher zuweist, kann fehlschlagen, besonders auf Systemen, die eingeschränkter sind und normalerweise keine Ausnahmen unterstützen.

Wirklich, die Ausnahmen sind der beste Weg zu gehen.