Jeżeli destruktor działa na operacji, która może zniszczyć obiekt, powinien on z-wrapować taką operację w blok try i obsłużyć wyjątek lokalnie. Destruktory nie powinny rzucać wyjątków! Jeżeli podczas zwijania stosu destruktor rzuci wyjątkiem którego sam nie złapał program zostanie zterminowany.
#include <iostream> using namespace std; struct MyClass { ~MyClass() /* noexcept */ { throw std::exception(); } }; int main() { MyClass a; return 0; }Wynik:
terminate called after throwing an instance of 'std::exception' what(): std::exception The program has unexpectedly finished.Kilka zasad dotyczących tworzenia, rzucania i łapania wyjątków:
- Obiekty wyjątków, które są rzucane, muszą mieć dostępny destruktor, a także konstruktor kopiujący lub konstruktor move.
- Najbardziej wyspecjalizowany catch musi pojawić się jako pierwszy.
- Aby wyrzucić wyjątek wyżej, trzeba podać throw bez wyrażenia (throw;).
- Aby złapać wyjątek każdego typu trzeba skorzystać z catch(...), który powinien zawsze pojawiać się jako ostatni.
#include <iostream> #include <typeinfo> #include <stdexcept> using namespace std; void old() { throw std::runtime_error("old()"); } void young() { try { old(); } catch(const std::bad_cast& e) { cout << e.what() << endl; } catch(...) { cout << "unknown, rethrow" << endl; throw; } } int main() { try { young(); } catch(const std::runtime_error& e) { cout << "in main: " << e.what() << endl; } return 0; }Wynik:
unknown, rethrow in main: old()
Lista inicjalizacyjna konstruktora
Konstruktor nie może złapać wyjątków rzuconych z listy inicjalizacyjnej. Aby się przed tym ustrzec, blok try musi znajdować się przed dwukropkiem. Zachowanie jest podobne w działaniu do zwykłych try-catch, z tą różnicą że złapany wyjątek zostanie ponownie wyrzucony z bloku.#include <iostream> #include <stdexcept> using namespace std; struct Member { Member() { } Member(Member& m) { throw std::runtime_error("Constructor"); } }; struct BadCalss { Member member; BadCalss(Member& m) try : member(m) { // body } catch(...) { cout << "some bug" << endl; } }; int main() { Member m; BadCalss bc(m); return 0; }Wynik:
some bug terminate called after throwing an instance of 'std::runtime_error' what(): Constructor The program has unexpectedly finished.
noexcept
W nowym standardzie pojawił się specjalny specyfikator noexcept. Jeżeli wiemy, że funkcja nie będzie rzucać wyjątku, może z tej informacji skorzystać i programista i kompilator. Wspominał o tym również Scott Meyers w swoim wykładzie na Going Native 2013. Kompilator w takim przypadku wygeneruje znacznie mniej kodu, który będzie bardziej optymalny w działaniu.#include <iostream> #include <stdexcept> using namespace std; void fun() noexcept { throw std::runtime_error("fun()"); } // gun() ma taki sam specyfikator rzucanych typów jak fun() void gun() noexcept(noexcept(fun())) { cout << "gun()" << endl; } int main() { fun(); return 0; }Wynik:
terminate called after throwing an instance of 'std::runtime_error' what(): fun() The program has unexpectedly finished.W rezultacie noexpect powinno być stosowane w dwóch przypadkach. Kiedy jesteśmy przekonani, że funkcja nie rzuci wyjątku albo/i gdy nie chcemy obsługiwać błędu. Jeżeli z funkcji (oznaczonej jako noexcept) zostanie jednak rzucony wyjątek, natychmiast zawołane zostanie std::terminate().
Inny przykład, w którym wyjątek przelatuje przez funkcję z noexcept, co powoduje zterminowanie programu.
#include <iostream> #include <stdexcept> using namespace std; void fun_a() { throw std::runtime_error("bug"); } void fun_b() noexcept { fun_a(); } void fun_c() { try { fun_b(); } catch(...) { cout << "Exception caught" << endl; } } int main() { fun_c(); }Wynik:
terminate called after throwing an instance of 'std::runtime_error' what(): bug The program has unexpectedly finished.Niektóre kontenery jak np. std::vector, sprawdzają (podczas realokacji wektora), czy "move konstruktor" jest oznaczony jako noexcept. Jeżeli tak nie jest zostanie zwołany zwykły konstruktor kopiujący, co jest operacją dużo wolniejszą. Odpowiedni eksperyment stworzyłem w innym artykule.
Poza "move konstruktorem" noexcept standardowo powinno znaleźć się również w deklaracji destruktora.
Stare - throw()
W poprzedniej wersji standardu istniało słowo kluczowe throw(), które obecnie jest uznawane za przestarzałe. Można było za jego pomocą wyspecyfikować jakiego rodzaju wyjątki mogą być wyrzucone z funkcji, albo (odpowiednik noexpect) że funkcja nie rzuci żadnego wyjątku.int fun(std::string& b) throw();
Brak komentarzy:
Prześlij komentarz