8Wyjaśnij poziomy bezpieczeństwa wyjątków (exception safety levels) oraz to, jak słowo kluczowe `noexcept` wpływa na operacje przenoszenia, kontenery i generowanie kodu.
Poziomy bezpieczeństwa wyjątków określają, co pozostaje prawdą, jeśli operacja rzuci wyjątkiem. Podstawowa gwarancja oznacza, że niezmienniki są zachowane i nie dochodzi do wycieków zasobów, choć stan mógł ulec zmianie. Silna gwarancja to inaczej semantyka zatwierdź-lub-wycofaj (commit-or-rollback): w przypadku błędu obserwowalny stan pozostaje bez zmian. Gwarancja braku wyjątków (nothrow guarantee) oznacza, że operacja wcale nie rzuca wyjątków. Powszechne techniki osiągnięcia tych gwarancji obejmują mechanizm RAII (Resource Acquisition Is Initialization), wykonywanie operacji na obiektach tymczasowych, idiom kopiuj-i-zamień (copy-and-swap) oraz porządkowanie modyfikacji w taki sposób, aby zatwierdzenie nastąpiło dopiero po pomyślnym wykonaniu pracy narażonej na rzucenie wyjątku. Słowo kluczowe `noexcept` to swego rodzaju kontrakt: jeśli funkcja `noexcept` rzuci wyjątkiem, zostanie wywołana funkcja `std::terminate`. Wpływa to również na kod generyczny i kontenery: na przykład podczas realokacji `std::vector` może przenosić elementy, jeśli konstruktor przenoszący jest `noexcept`; w przeciwnym razie może je kopiować, często za pomocą funkcji `std::move_if_noexcept`, by utrzymaować gwarancje względem wyjątków. Odpowiednie zastosowanie `noexcept` może również pomóc w generowaniu kodu i jego optymalizacji poprzez zmniejszenie wymaganych ścieżek propagacji wyjątków, pod warunkiem jednak, że powyższa obietnica jest rzeczywiście poprawna.
Odpowiedz na to pytanie z trenerem AI
9Wyjaśnij zastosowanie `std::expected` lub równoważnych typów wynikowych (outcome types) jako alternatywy dla wyjątków w operacjach mogących zakończyć się niepowodzeniem (fallible operations).
`std::expected<T, E>` reprezentuje pomyślną wartość `T` lub jawny błąd `E`, sprawiając, że niepowodzenie staje się częścią zwracanego typu funkcji, zamiast polegać na propagacji wyjątków. Jest to przydatne w przypadku operacji mogących zakończyć się błędem, gdzie błędy są spodziewane, a wywołujący powinni obsługiwać je lokalnie – takich jak parsowanie, walidacja, wywołania sieciowe, odpytywanie pamięci masowej (storage lookups) oraz ponawialne operacje backendowe. Typ błędu powinien przenosić ustrukturyzowane informacje, takie jak kod błędu, kategoria, możliwość ponowienia, komunikat lub mapowanie na kody HTTP (Hypertext Transfer Protocol) / RPC (Remote Procedure Call). Typy oczekiwane/wynikowe (expected/outcome) pozwalają na kompozycję poprzez sprawdzanie i propagację błędów, a w API (Application Programming Interface) w stylu C++23 mogą wykorzystywać operacje monadyczne, takie jak `and_then`, `transform` oraz `or_else`, do łączenia kroków w łańcuchy bez głęboko zagnieżdżonych instrukcji warunkowych. W porównaniu z wyjątkami, czynią one przepływ sterowania i kontrakty API jawnymi, a ponadto dobrze sprawdzają się w środowiskach bez wyjątków lub na granicach ABI (Application Binary Interface); wyjątki mogą nadal być odpowiednie dla rzadkich, nielokalnych lub prawdziwie wyjątkowych awarii, w zależności od przyjętej polityki projektu.
Odpowiedz na to pytanie z trenerem AI