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