Засовываем исключение в исключение
#опытным
Вы знали, что в плюсах есть вложенные исключения? Такие исключения могут хранить в себе несколько исключений. Сегодня мы посмотрим, что это за зверь такой.
Начнем с применения. В прошлом посте мы создавали новое исключение на основе строки ошибки обрабатываемого исключения. В этом случае нужно писать определенное количество бойлерплейта и теряется информация о типе изначального исключения. Чтобы избежать этих проблем, мы можем бросить новое исключение, которое будет в себе содержать старое:
Теперь исключение, которое вылетит из HandlingCalculations будет на самом деле содержать 3 исключения: от базы данных, от ComplicatedCalculations и от HandlingCalculations.
Вложенные исключения существуют с С++11 и очень интересно устроены. Рассмотрим несколько упрощенные версии сущностей, которые находятся под капотом механизма вложенных исключений. Есть класс std::nested_exception:
Этот класс ответственен за захват текущего исключения с помощью вызова std::current_exception().
Дальше имеется класс, который хранит в себе все множество исключений:
Объекты этого класса наследуются от nested_exception, в котором захвачено старое исключение, и от Except - нового исключения.
Ну и последний компонент - std::throw_with_nested:
При вызове throw_with_nested создается объект Nested_exception на основе переданного типа исключения и, неявно, nested_exception, которых сохраняет в себе указатель на старое исключение.
Получается, что мы при каждом вызове throw_with_nested подмешиваем новое исключение к старому с помощью множественного наследования.
Очень прикольная техника, которая позволяет строить цепочки объектов. Это как тупл, только расширяемый в рантайме.
Это все хорошо и интересно. Прокидывать вложенные исключения мы научились. Но рано или поздно их придется обработать. Как это сделать? Об этом будем говорить в следующем посте.
Inherit knowledge from your ancestor. Stay cool.
#cppcore #cpp11
#опытным
Вы знали, что в плюсах есть вложенные исключения? Такие исключения могут хранить в себе несколько исключений. Сегодня мы посмотрим, что это за зверь такой.
Начнем с применения. В прошлом посте мы создавали новое исключение на основе строки ошибки обрабатываемого исключения. В этом случае нужно писать определенное количество бойлерплейта и теряется информация о типе изначального исключения. Чтобы избежать этих проблем, мы можем бросить новое исключение, которое будет в себе содержать старое:
void ComplicatedCalculations() try {
// use db
} catch (std::exception& ex) {
std::throw_with_nested(std::runtime_error("Complicated Calculations Error"));
}
void HandlingCalculations() try {
ComplicatedCalculations();
} catch (std::exception& ex) {
std::throw_with_nested(std::runtime_error("Handling Calculations Error"));
}Теперь исключение, которое вылетит из HandlingCalculations будет на самом деле содержать 3 исключения: от базы данных, от ComplicatedCalculations и от HandlingCalculations.
Вложенные исключения существуют с С++11 и очень интересно устроены. Рассмотрим несколько упрощенные версии сущностей, которые находятся под капотом механизма вложенных исключений. Есть класс std::nested_exception:
class nested_exception
{
exception_ptr _M_ptr;
public:
/// The default constructor stores the current exception (if any).
nested_exception() noexcept : _M_ptr(current_exception()) { }
...
};
Этот класс ответственен за захват текущего исключения с помощью вызова std::current_exception().
Дальше имеется класс, который хранит в себе все множество исключений:
template<typename Except>
struct Nested_exception : public Except, public nested_exception
{
explicit Nested_exception(const Except& ex)
: Except(ex) { }
};
Объекты этого класса наследуются от nested_exception, в котором захвачено старое исключение, и от Except - нового исключения.
Ну и последний компонент - std::throw_with_nested:
template<typename Tp>
[[noreturn]]
inline void throw_with_nested(Tp&& t)
{
throw Nested_exception<remove_cvref_t<Tp>>{std::forward<Tp>(t)};
}
При вызове throw_with_nested создается объект Nested_exception на основе переданного типа исключения и, неявно, nested_exception, которых сохраняет в себе указатель на старое исключение.
Получается, что мы при каждом вызове throw_with_nested подмешиваем новое исключение к старому с помощью множественного наследования.
Очень прикольная техника, которая позволяет строить цепочки объектов. Это как тупл, только расширяемый в рантайме.
Это все хорошо и интересно. Прокидывать вложенные исключения мы научились. Но рано или поздно их придется обработать. Как это сделать? Об этом будем говорить в следующем посте.
Inherit knowledge from your ancestor. Stay cool.
#cppcore #cpp11
🔥17❤9👍5
Раскрываем вложенное исключение
#опытным
Каша заварилась, пришло время расхлебывать. В прошлом посте мы успешно завернули кучу исключений в одно, теперь нужно как-то все обратно развернуть. Для этого существует функция std::rethrow_if_nested. Работает она вот так:
Так как объект вложенного исключения - это наследник std::nested_exception, то мы имеем право динамически привести указатель переданного в rethrow_if_nested исключения к std::nested_exception. Если каст прошел успешно, то бросается исключение, сохраненное в nested_exception. Если каст провалился, значит
Обычно в статьях по этой теме приводят мягко говоря странные примеры того, как надо обрабатывать вложенные исключения:
Встречайте вложенные try-catch с заранее известной глубиной вложенности. Это конечно никуда не годится.
В реальности при повсеместном использовании вложенных исключений мы не знаем, сколько раз нам нужно сделать rethrow_if_nested. Но при этом мы точно знаем, что если исключение последнее в цепочке, то rethrow_if_nested ничего не сделает. Это же идеальное условие для остановки рекурсии!
Рекурсия решает проблему неопределенного количества уровней вложенности исключений и помогает удобно подряд печатать соответствующие сообщения об ошибках:
В этом случае на добавление дополнительной информации к исключению вы тратите всего 1 строчку, которая мало чем отличается от возбуждения нового исключения. Плюс в коде логики проекта вы одной строчкой обрабатываете все вложенные исключения. Красота!
Очень нишевая функциональность, которую вы вряд ли встретите в реальных проектах. Однако будет прикольно попробовать ее в своих пет-проектах, если вы не гнушаетесь исключений.
Have a good error handling system. Stay cool.
#cppcore #cpp11
#опытным
Каша заварилась, пришло время расхлебывать. В прошлом посте мы успешно завернули кучу исключений в одно, теперь нужно как-то все обратно развернуть. Для этого существует функция std::rethrow_if_nested. Работает она вот так:
class nested_exception
{
exception_ptr _M_ptr;
public:
/// The default constructor stores the current exception (if any).
nested_exception() noexcept : _M_ptr(current_exception()) { }
[[noreturn]] void rethrow_nested() const {
if (_M_ptr)
// just throw _M_ptr
rethrow_exception(_M_ptr);
std::terminate();
}
...
};
template<class E>
void rethrow_if_nested(const E& e) {
if (auto p = dynamic_cast<const std::nested_exception*>(std::addressof(e)))
p->rethrow_nested();
}
Так как объект вложенного исключения - это наследник std::nested_exception, то мы имеем право динамически привести указатель переданного в rethrow_if_nested исключения к std::nested_exception. Если каст прошел успешно, то бросается исключение, сохраненное в nested_exception. Если каст провалился, значит
E - это уже не наследник nested_exception и у нас в руках самое первое исключение в цепочке.Обычно в статьях по этой теме приводят мягко говоря странные примеры того, как надо обрабатывать вложенные исключения:
int main() {
try {
Login("[email protected]", "secret");
} catch (SecurityError &e) {
std::cout << "Caught a SecurityError";
try {
std::rethrow_if_nested(e);
} catch (AuthenticationError &e) {
std::cout << "\nNested AuthenticationError: " << e.Email;
}
}
std::cout << "\nProgram recovered";
}Встречайте вложенные try-catch с заранее известной глубиной вложенности. Это конечно никуда не годится.
В реальности при повсеместном использовании вложенных исключений мы не знаем, сколько раз нам нужно сделать rethrow_if_nested. Но при этом мы точно знаем, что если исключение последнее в цепочке, то rethrow_if_nested ничего не сделает. Это же идеальное условие для остановки рекурсии!
Рекурсия решает проблему неопределенного количества уровней вложенности исключений и помогает удобно подряд печатать соответствующие сообщения об ошибках:
void print_exception(const std::exception &e, int level = 0) {
std::cerr << std::string(level, ' ') << "exception: " << e.what() << '\n';
try {
std::rethrow_if_nested(e);
} catch (const std::exception &nestedException) {
print_exception(nestedException, level + 1);
} catch (...) {
}
}
void ComplicatedCalculations() try {
// use db
} catch (std::exception &ex) {
std::throw_with_nested(std::runtime_error("Complicated Calculations Error"));
}
void HandlingCalculations() try { ComplicatedCalculations(); } catch (std::exception &ex) {
std::throw_with_nested(std::runtime_error("Handling Calculations Error"));
}
int main() {
try {
HandlingCalculations();
} catch (const std::exception &e) {
print_exception(e);
}
}
// OUTPUT:
// Handling Calculations Error
// Complicated Calculations Error
// Some DB ErrorВ этом случае на добавление дополнительной информации к исключению вы тратите всего 1 строчку, которая мало чем отличается от возбуждения нового исключения. Плюс в коде логики проекта вы одной строчкой обрабатываете все вложенные исключения. Красота!
Очень нишевая функциональность, которую вы вряд ли встретите в реальных проектах. Однако будет прикольно попробовать ее в своих пет-проектах, если вы не гнушаетесь исключений.
Have a good error handling system. Stay cool.
#cppcore #cpp11
2🔥14❤11👍9🤣2