programowanieProgramista systemowy C++

Opowiedz o mechanizmach działania wyjątków (exceptions) w C++. Jak poprawnie obsługiwać błędy i po co jest noexcept?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Historia pytania:

Mechanizm obsługi wyjątków został wprowadzony w C++ w celu zapewnienia bardziej niezawodnej i zorganizowanej obsługi błędów, w przeciwieństwie do kodów zwrotnych. Z biegiem lat składnia rozszerzyła się, pojawiły się specyfikatory typu noexcept do kontroli anulowania wyrzucania wyjątków z funkcji.

Problem:

Niepoprawna praca z wyjątkami często prowadzi do wycieków pamięci, niezdefiniowanego zachowania lub awarii aplikacji. Jeśli nie uwzględnić wyrzucania wyjątków z konstruktorów, destruktorów lub podczas pracy z zasobami, pojawia się poważny problem z stanem programu.

Rozwiązanie:

Podstawowa składnia — użycie try-catch. Wyjątki mogą być dowolnego typu, ale zaleca się dziedziczenie użytkowników od std::exception. Słowo kluczowe noexcept (C++11+) wskazuje, że funkcja nie powinna wyrzucać wyjątków; wyrzucenie wyjątku z funkcji noexcept wywołuje std::terminate().

Przykład kodu:

#include <iostream> #include <stdexcept> void func() noexcept(false) { throw std::runtime_error("Błąd!"); } int main() { try { func(); } catch(const std::exception& ex) { std::cout << ex.what(); } }

Kluczowe cechy:

  • Wyjątki zapewniają automatyczne unwind (odwijanie stosu)
  • noexcept pozwala kompilatorowi optymalizować, jeśli funkcja gwarantuje, że nie wyrzuca
  • Tylko std::exception i jej pochodne gwarantują obecność metody what()

Pytania z podstępem.

Czy dozwolone jest wyrzucanie wyjątków w destruktorze?

Nie, wyrzucenie wyjątku z destruktora podczas odwijania stosu prowadzi do std::terminate(). Wyjątki w destruktorze należy przechwytywać wewnątrz samego destruktora.

Co się stanie, jeśli wyrzucisz wyjątek z funkcji noexcept?

Zostanie wywołane std::terminate, program zakończy pracę awaryjnie. noexcept — to surowy kontrakt.

Czy można łapać wyjątki po wartości? Czym to grozi?

Można łapać po wartości, ale obiekt jest kopiowany (object slicing). Zaleca się używanie const &.

Przykład kodu:

try { throw std::out_of_range("błąd"); } catch (const std::exception& e) { /* ... */ }

Typowe błędy i anty-wzorce

  • Używanie throw w destruktorach
  • Przechwytywanie wyjątków catch(...) bez logowania
  • Nie wskazanie noexcept tam, gdzie to możliwe

Przykład z życia

Negatywny przypadek

Kod wyrzucał wyjątki w destruktorach zasobów; przy awaryjnym zakończeniu stos był niepoprawnie odwijany, zasoby pozostały w wycieku.

Plusy:

  • Szybkie wykrywanie błędów

Minusy:

  • Programy często kończyły się błędem terminate

Pozytywny przypadek

Krytyczne metody były oznaczone jako noexcept, destruktory obsługiwały wszystkie wyjątki wewnątrz siebie. Zastosowano catch po referencji, wszystkie błędy były logowane.

Plusy:

  • Niezawodność, brak wycieków
  • Ekstremalnie przewidywalne zachowanie

Minusy:

  • Zwiększona surowość wobec programisty podczas pisania poprawnego, "czystego" kodu