2013-02-05 11 views
9

Mam klasę, która może rzucić wyjątek w swoim konstruktorze. Jak mogę zadeklarować instancję tej klasy w bloku try/catch, nadal udostępniając ją w odpowiednim zakresie?Podejście RAII do przechwytywania wyjątków konstruktora

try { MyClass lMyObject; } 
catch (const std::exception& e) { /* Handle constructor exception */ } 

lMyObject.DoSomething(); // lMyObject not in scope! 

Czy istnieje alternatywny sposób do osiągnięcia tego celu, przy jednoczesnym poszanowaniu RAII idiom?

Wolałbym nie używać metody init() dla konstrukcji dwufazowej. Jedyne co mogłam wymyślić było:

MyClass* lMyObject; 

try { lMyObject = new MyClass(); } 
catch (const std::exception& e) { /* Handle constructor exception */ } 

std::shared_ptr<MyClass> lMyObjectPtr(lMyObject); 
lMyObjectPtr->DoSomething(); 

działa OK, ale nie jestem zadowolony z surowego wskaźnik zakres i wskaźnik pośredni. Czy to tylko kolejny wątek C++?

+2

Przenieść wywołanie metody DoSomething() na blok try? – milleniumbug

+1

** Nigdy ** nie łapaj wyjątków kopią. Trzymaj się * stałych odniesień *. –

+1

W drugim przykładzie, jeśli konstruktor rzucił, "lMyObject" jest niezainicjowanym wskaźnikiem. – aschepler

Odpowiedz

5

Jeśli konstruktor rzuca, oznacza to, że obiekt nie mógł się zainicjować, a tym samym nie rozpoczął się jego istnienie.

MyClass* lMyObject; 
try { lMyObject = new MyClass(); } 
catch (std::exception e) { /* Handle constructor exception */ } 

W powyższym przykładzie, jeśli konstruktor zgłasza wyjątek lMyObject zostaje niezainicjowanego Innymi słowy, wskaźnik zawiera wartość nieokreśloną.

Zobacz klasyczny Constructor Failures o szczegółowe wyjaśnienie:

Możemy podsumować C++ modelu konstruktora następująco:

Albo:

(a) powraca konstruktor normalnie dobiega końca lub instrukcja return, a obiekt istnieje.

Lub:

(b) Wyjścia konstruktor emitując wyjątek, a obiekt nie tylko nie teraz istnieje, ale nigdy nie istniał.

Nie ma innych możliwości.

0

Nie musisz używać shared_ptr, użyj unique_ptr:

std::unique_ptr<MyClass> pMyObject; 
try { pMyObject.reset(new MyClass()); } 
catch (std::exception &e) { /* Handle constructor exception */ throw; } 
MyClass &lMyObject = *pMyObject; 

lMyObject.DoSomething(); 

Oczywiście, to Twoim obowiązkiem jest upewnić się, że program nie wchodzi przez blok catch bez albo inicjalizacji pMyObject lub opuszczający funkcja (np. przez return lub throw).

Jeśli są dostępne, można użyć Boost.Optional aby uniknąć używania pamięci sterty:

boost::optional<MyClass> oMyObject; 
try { oMyObject.reset(MyClass()); } 
catch (std::exception &e) { /* Handle constructor exception */ throw; } 
MyClass &lMyObject = *oMyObject; 

lMyObject.DoSomething(); 
+0

Odkłada wskaźnik 'NULL' w przypadku zgłoszenia wyjątku. –

+0

@MaximYegorushkin, który zależy całkowicie od tego, co '/ * Handle constructor exception * /' robi. – ecatmur

+0

Dobrym testem dla twojej porady jest zamiana '/ * Handle constructor exception * /' z pustym łańcuchem. Twoja rada nie przejdzie tego testu. –

-1

Można skonfigurować MojaKlasa Kopią konstruktora zaakceptować wejście na śmieci, a tym samym skutecznie deklarując wskaźnik wraz ze swoją deklaracją obiektu. Następnie można manually call domyślnego konstruktora w bloku try:

MyClass lMyObject(null); // calls copy constructor 
try { 
    new (lMyObject) MyClass(); // calls normal constructor 
} 
catch (const std::exception& e) { /* Handle constructor exception */ } 

lMyObject.DoSomething(); 
+0

Wywołuje konstruktor 'MyClass' dwa razy, destruktor raz . –

+0

W prawo - aby ten wzorzec działał, musiałbyś napisać swój konstruktor kopii tak, aby nie przydzielał żadnych zasobów. To trochę hackish. –

1

Konstruktorzy są na oddanie obiektu do stanu spójności oraz ustanowienie niezmienników klasy.Zezwolenie na wyjątek w celu uniknięcia konstruktora oznacza, że ​​coś w tym konstruktorze nie zostało ukończone, więc teraz obiekt znajduje się w jakimś nieznanym dziwnym stanie (który może również obejmować przecieki zasobów). Ponieważ konstruktor obiektu nie został zakończony, kompilator nie spowoduje wywołania jego destruktora. Być może to, czego szukasz, to złapać wyjątek wewnątrz konstruktora. Zakładając, że nie zostanie ponownie wyświetlony, spowoduje to zakończenie wykonywania przez konstruktora, a obiekt jest już w pełni ukształtowany.

2

Najlepszym sposobem pisania kodu jest to: -

MyClass lMyObject; 
lMyObject.DoSomething(); 

Brak Trys, zaczepy, czy wskaźniki.

Jeśli konstruktor rzuca, wówczas funkcja DoSomething nie może zostać wywołana. Co jest właściwe: jeśli konstruktor rzucił, obiekt nigdy nie został skonstruowany.

Co ważniejsze, nie łapaj (a nawet nie chwytaj/nie rzucaj) wyjątków, chyba że masz z nimi coś konstruktywnego. Niech wyjątki wykonują swoją pracę i marszczą, aż coś, co wie, jak sobie z nimi poradzić, może wykonać swoją pracę.

+0

Dziękuję za sugestię, myślę, że to dobra uwaga. –

Powiązane problemy