8Обяснете нивата на безопасност спрямо изключения и как `noexcept` влияе на операциите за преместване, контейнерите и генерирането на код.
Нивата на безопасност спрямо изключения описват какво остава вярно, ако дадена операция хвърли изключение. Основната гаранция означава, че инвариантите са запазени и ресурсите не изтичат, въпреки че състоянието може да се е променило. Силната гаранция означава семантика на потвърждаване или отмяна (commit-or-rollback): при неуспех, видимото състояние остава непроменено. Гаранцията без хвърляне (nothrow guarantee) означава, че операцията не хвърля изключения. Често срещаните техники включват RAII (Resource Acquisition Is Initialization), извършване на работа върху временни обекти, копиране и размяна (copy-and-swap) и подреждане на промените така, че потвърждаването да се случи едва след като работата, която може да хвърли изключение, е успешна. `noexcept` е договор: ако `noexcept` функция хвърли изключение, се извиква `std::terminate`. Това също влияе на обобщения код и контейнерите: например, по време на преалокация `std::vector` може да премести елементите, когато конструкторът за преместване е `noexcept`; в противен случай може да ги копира, често чрез `std::move_if_noexcept`, за да запази гаранциите за изключения. `noexcept` може също да помогне при генерирането или оптимизацията на код, като намали необходимите пътища за разпространение на изключения, но само когато обещанието е коректно.
std::vector<T> v;
// During reallocation, an implementation may effectively do:
T* dest = allocate(new_cap);
for (T& x : old_storage) {
construct(dest++, std::move_if_noexcept(x));
}
Опитайте да отговорите на този въпрос с треньор с ИИ
9Обяснете `std::expected` или еквивалентните типове за резултат като алтернативи на изключенията при операции, които могат да завършат с грешка.
`std::expected<T, E>` представлява или успешна стойност `T`, или изрична грешка `E`, като по този начин превръща неуспеха в част от връщания тип на функцията, вместо да разчита на разпространението на изключения. Този подход е полезен за операции, податливи на грешки, при които грешките са очаквани и извикващият код трябва да ги обработи локално, като например синтактичен анализ (парсване), валидация, мрежови извиквания, търсене в хранилища за данни и бекенд операции, позволяващи повторен опит. Типът на грешката трябва да носи структурирана информация, като например код на грешката, категория, индикация дали операцията може да се повтори, съобщение или съответствие с HTTP (Hypertext Transfer Protocol) / RPC (Remote Procedure Call) код. Типовете за резултат се композират чрез проверка и препредаване на грешки, а в програмни интерфейси в стила на C++23 могат да се използват монадични операции като `and_then`, `transform` и `or_else` за верижно свързване на стъпки без дълбоко вложени условни конструкции. В сравнение с изключенията, те правят потока за управление и договорите на програмните интерфейси изрични и работят добре в код без изключения или през границите на двоичния приложен интерфейс (ABI - Application Binary Interface); изключенията все още могат да бъдат подходящи за редки, нелокални или наистина изключителни ситуации, в зависимост от правилата на конкретния проект.
enum class Errc { BadRequest, NotFound, Timeout, DbUnavailable };
struct Error {
Errc code;
bool retryable;
std::string message;
};
std::expected<User, Error> load_user(UserId id);
HttpResponse handle(UserId id) {
auto user = load_user(id);
if (!user) {
switch (user.error().code) {
case Errc::BadRequest: return {400, user.error().message};
case Errc::NotFound: return {404, "not found"};
case Errc::Timeout: return {503, "try again"};
case Errc::DbUnavailable: return {503, "service unavailable"};
}
}
return {200, serialize(*user)};
}
Опитайте да отговорите на този въпрос с треньор с ИИ