System wyjątków w C++ opiera się na mechanizmie try-catch oraz słowie kluczowym throw. W przypadku wystąpienia sytuacji wyjątkowej (np. błąd zasobu, naruszenie invariantów), wewnątrz bloku try inicjowane jest (wyrzucane) wyjątek za pomocą operatora throw. Operacje wyszukiwania odpowiedniego obsługującego (catch) odbywają się poprzez przeszukiwanie stosu do pierwszego pasującego typu.
Wažne szczegóły:
std::exception).noexcept.Projektowanie:
catch(const std::exception& e).Przykład kodu:
#include <iostream> #include <stdexcept> void mayFail(bool fail) { if (fail) throw std::runtime_error("Błąd procesu"); } int main() { try { mayFail(true); } catch (const std::exception& ex) { std::cout << "Złapano wyjątek: " << ex.what() << std::endl; } }
Co się stanie, jeśli wyjątek zostanie wyrzucony z destruktora podczas przekazywania innego wyjątku w górę stosu?
Odpowiedź: Program zakończy się awaryjnie przez wywołanie std::terminate(), ponieważ podczas obsługi wyjątku nie można generować nowego wyjątku (double exception), w przeciwnym razie narusza to odwijanie stosu.
Przykład:
struct CrashOnDestruct { ~CrashOnDestruct() noexcept(false) { throw std::runtime_error("Błąd w destruktorze!"); } }; void func() { CrashOnDestruct obj; throw std::logic_error("Obsługa błędu"); } int main() { try { func(); } catch (...) { } } // Zakończy się przez std::terminate()
Historia
W dużej aplikacji finansowej wyjątki były rzucane z destruktora klasy-opakowania nad bazą danych. Jedna z kluczowych transakcji w przypadku wystąpienia błędu inicjowała wyjątek, a przy wyjściu przez unwind był wywoływany destruktor, który również rzucał błąd. Cała aplikacja kończyła się awarią, tracąc pracę użytkowników. Programiści musieli pilnie zastąpić throw w destruktorach zapisywaniem logów i poprawną obsługą.
Historia
Mikrousługa obsługiwała wyjątki typu: catch(std::exception). Jeden z wątków wyrzucał wyjątki typów użytkownika (nie dziedziczyły od std::exception). Takie błędy nie były chwytane, co prowadziło do niespodziewanego zerwania połączenia i wycieku pamięci.
Historia
Brak specyfikatora noexcept w metodach przenoszenia kontenerów spowodował, że standardowa biblioteka używała wolnej kopii zamiast szybkiej operacji move; znaczny spadek wydajności został wykryty tylko podczas testów obciążeniowych.