2016-08-02 17 views
8

Ich habe eine C++ - Klasse, die eine Ausnahme vom Konstruktor bei einem Fehler auslöst. Wie kann ich eine lokale Instanz dieser Klasse zuweisen (ohne new zu verwenden) und alle möglichen Ausnahmen behandeln, während der Blockbereich try so klein wie möglich gehalten wird?Wie fangen Sie eine Konstruktorausnahme?

Im Wesentlichen, ich suche für die C++ äquivalent das folgende Java-Idioms:

boolean foo() { 
    Bar x; 
    try { 
     x = new Bar(); 
    } catch (Exception e) { 
     return false; 
    } 
    x.doSomething(); 
    return true; 
} 

Ich will nicht Ausnahmen von x.doSomething(), fangen nur den Konstruktor.

Ich nehme an, was ich suche, ist eine Möglichkeit, die Deklaration und die Initialisierung von x zu trennen.

Ist es möglich, dies ohne Verwendung von Heap-Zuordnungen und Zeigern zu erreichen?

+0

Ja. Setzen Sie alles auf den erfolgreichen Pfad im try-Block. Sie bleiben im Funktionsumfang. – StoryTeller

+0

@StoryTeller was, wenn ich keine Ausnahmen von z. 'x.doSomething()', nur Ausnahmen vom Konstruktor? –

+0

@AndrewSun Verwenden Sie verschiedene Ausnahmen oder setzen Sie 'x.doSomething()' in einen inneren Ausnahmeblock. – Holt

Antwort

11

Sie std::optional von C++ 17 verwenden können:

bool foo() { 
    std::optional<Bar> x; // x contains nothing; no Bar constructed 
    try { 
     x.emplace();  // construct Bar (via default constructor) 
    } catch (const Exception& e) { 
     return false; 
    } 
    x->doSomething();  // call Bar::doSomething() via x (also (*x).doSomething()) 
    return true; 
} 
+0

@ Jarod42 Ich zögerte, wie man es für defualt ctor von 'Bar' schreibt. 'x.emplace()'? 'x.emplace ({})'? – songyuanyao

+1

Es ist 'x.emplace()' für den Standardkonstruktor. Das spätere wird nicht kompiliert. – Jarod42

+0

@ Jarod42 Danke! – songyuanyao

2

Sie haben zwischen Variante

bool foo() { 
    std::unique_ptr<Bar> x; 
    try { 
     x = std::make_unique<Bar>(); 
    } catch (const BarConstructorException& e) { 
     return false; 
    } 
    x->doSomething(); 
    return true; 
} 

oder

bool foo() { 
    try { 
     Bar x; 
     x.doSomething(); 
    } catch (const BarConstructorException& e) { 
     return false; 
    } 
    return true; 
} 
2

Normalerweise wählen, ob Sie Heapzuweisungen vermeiden wollen, können Sie die Deklaration einer lokalen Variablen aus seiner Definition trennen. Also, wenn Sie waren alles in einer einzigen Funktion zu kombinieren, würden Sie das gesamte Spektrum der x mit try/catch Block umgeben zu tun haben:

boolean foo() { 
    try { 
     Bar x; 
     x.doSomething(); 
    } catch (Exception e) { 
     return false; 
    } 
    return true; 
} 
5

Ja, es ist möglich, wenn Sie den gesamten Code setzen in der try Klausel, für Beispiel durch eine mit function try block (unnötige Verschachtelung und Scoping zu vermeiden):

bool foo() try 
{ 
    Bar x; 
    x.doSomething(); 
    return true; 
} 
catch (std::exception const& e) 
{ 
    return false; 
} 

Oder im try Klausel Aufruf einer anderen Funktion, die die eigentliche Arbeit macht:

void real_foo() 
{ 
    Bar x; 
    x.doSomething(); 
} 

bool foo() try 
{ 
    real_foo(); 
    return true; 
} 
catch (std::exception const& e) 
{ 
    return false; 
} 

Beachten Sie, dass es oft keine gute Idee ist, Ausnahmen in einem Konstruktor auszulösen, da dies die Konstruktion des Objekts stoppt und dessen Destruktor nicht aufgerufen wird.


Wie Holt erwähnt, wird dies auch Ausnahmen von dem doSomething Anruf fangen. Es gibt zwei Möglichkeiten, das zu lösen:

  1. Die einfache und standardisierte Möglichkeit: Verwenden Zeiger.

  2. Verwenden Sie zweistufige Konstruktion: Verwenden Sie einen Standardkonstruktor, der keine Ausnahmen auslösen kann, und rufen Sie dann eine spezielle "Konstrukt" -Funktion auf, die Ausnahmen auslösen kann.

Der zweite Weg war üblich, vor C++ standardisiert war und ausführlich in Code für die Symbian system verwendet. Es ist nicht mehr üblich, da die Verwendung von Zeigern viel einfacher und einfacher ist, besonders heute mit guten Smartpointern. Ich empfehle wirklich nicht den zweiten Weg im modernen C++.

Der einfachste Weg ist natürlich, sicherzustellen, dass der Konstruktor keine Ausnahmen überhaupt werfen kann, oder wenn einer geworfen wird, dann sind sie von der Natur, dass das Programm nicht fortgesetzt werden kann und das Programm beendet wird. Wie in den Kommentaren zu Ihrer Frage angemerkt, sind Ausnahmen in C++ teuer, und dann haben wir auch das Problem der abgebrochenen Konstruktion, und alle Ausnahmen in C++ sollten nur in Ausnahmefällen verwendet werden. C++ ist kein Java, Sie sollten es nicht als solches behandeln, selbst wenn es ähnliche Konstrukte in beiden Sprachen gibt.

Wenn Sie immer noch Ausnahmen aus dem Konstruktor werfen möchten, gibt es tatsächlich eine dritte Möglichkeit, nur diejenigen zu fangen: Verwenden Sie eines der oben genannten Code-Beispiel, und nur bestimmte Ausnahmen werfen, doSomething kann nie werfen und dann diese spezifischen fangen nur Konstruktoren.

+0

Funktion try Block wird nicht benötigt. Regelmäßiges "Versuch fangen" ist genug. – Jarod42

+0

@ Jarod42, dann muss 'Bar' in einem anderen Block sein, nicht im Funktionslevelblock. – Ajay

+1

Dies wird das Verhalten des gegebenen Java-Codes nicht nachahmen, da Sie Ausnahmen von 'x.doSomething()' abfangen werden. – Holt

2

Nr Von Ihrem Java-Beispiel werden Sie zwischen diesen zwei Möglichkeiten zur Auswahl:

Ohne Zeiger:

bool foo() { 
    try { 
     Bar x; 
     x.doSomething(); 
    } catch (Exception e) { 
     return false; 
    } 
    return true; 
} 

mit Zeigern:

bool foo() { 
    Bar* x = nullptr; 
    try { 
     x = new Bar(); 
    } catch (Exception e) { 
     return false; 
    } 
    x->doSomething(); 

    delete x; // don't forget to free memory 

    return true; 
} 

Oder mit verwalteten Zeiger:

#include <memory> 

bool foo() { 
    std::unique_ptr<Bar> x; 
    try { 
     x = new Bar();    // until C++14 
     x = std::make_unique<Bar>(); // since C++14 
    } catch (Exception e) { 
     return false; 
    } 
    x->doSomething(); 
    return true; 
} 
+3

Vielleicht möchten Sie irgendwo ein "delete x" hinzufügen;) – CompuChip

+0

Normalerweise verwende ich verwaltete Zeiger. Mein schlechtes ^^ – wasthishelpful

+1

Yep. Der Nachteil des zweiten Beispiels, wie Sie es jetzt haben, ist, dass Sie wirklich Ausnahmen in 'x-> doSomething 'abfangen wollen, um sicherzustellen, dass' delete' ausgeführt wird, aber dann benötigen Sie einen separaten try-catch-Block mit einem erneuten Durchlauf - mögliche Kopfschmerzen, die der 'unique_ptr' gut löst. Gut, dass du es hinzugefügt hast. – CompuChip

5

Dieses Java-Idiom kann nicht gut in C++ übersetzt werden, da Bar x;Standardkonstruktor erfordert, selbst wenn Ihr echter Konstruktor erfordert, dass Argumente übergeben werden.

rate ich würde die Sprache in diesem Ausmaß zu kämpfen - Erweiterung des try Block ist ausreichend - aber wenn man wirklich dann Sie verengen wollen eine Funktion nutzen könnten und sich auf Rückgabewert Optimierung a zu vermeiden Wert kopieren:

Aber wirklich, könnten Sie eine bestimmte Ausnahme auf die Konstruktion werfen, so dass diese Funktion vollständig umgehen.

+1

RVO & Umzug Semantik machen dies die beste Lösung meiner Meinung nach. – StoryTeller

+0

Vor C++ 17, OP muss so auch Exception kopieren/verschieben Konstruktor behandeln. – Jarod42

+0

@ Jarod42, was hat sich C++ 17 geändert? (Nur um meiner eigenen Neugier willen). – StoryTeller

1

In der überarbeiteten Frage der OP die Forderung ergänzt, die

Ich will nicht Ausnahmen von x.doSomething() zu fangen, nur der Konstruktor [der lokalen Variablen].

Ein einfacher Weg, den Java-Code

boolean foo() { 
    Bar x; 
    try { 
     x = new Bar(); 
    } catch (Exception e) { 
     return false; 
    } 
    x.doSomething(); 
    return true; 
} 

& hellip zu übersetzen; auf C++, ist dann ein Optional_ Klasse zu verwenden (wie Barton-Nackmann Fallible, boost::optional oder C++ 17 std::optional)

auto foo() 
    -> bool 
{ 
    Optional_<Bar> xo; 
    try 
    { 
     xo.emplace(); 
    } 
    catch(...) 
    { 
     return false; 
    } 

    Bar& x = *xo; 
    // Possibly other code here, then: 
    x.doSomething(); 
    return true; 
} 

eine gute Alternative ist, dass Code zu umgestalten, wie folgt aus:

struct Something_failure {}; 

void do_something(Bar& o) 
{ 
    // Possibly other code here, then: 
    o.doSomething(); 
} 

auto foo() 
    -> bool 
{ 
    try 
    { 
     Bar x; 

     do_something(x); 
     return true; 
    } 
    catch(Something_failure const&) 
    { 
     throw; 
    } 
    catch(...) 
    {} 
    return false; 
} 

Wenn Sie die oben genannten Ansätze nicht mögen, dann können Sie immer für eine dynamisch zugewiesene Bar Instanz, z Verwenden einer std::unique_ptr für eine garantierte Bereinigung, die jedoch den allgemeinen Aufwand einer dynamischen Zuordnung hat. In Java wird fast jedes Objekt dynamisch zugewiesen, so dass dies kein ernsthafter Nachteil zu sein scheint. Aber in C++ sind die meisten Objekte superschnell stack-allocated, so dass eine dynamische Zuweisung eine langsame Operation im Vergleich zu gewöhnlichen Operationen ist, so dass die mögliche konzeptionelle Einfachheit der dynamischen Zuweisung dagegen gewichtet werden muss.

Verwandte Themen