Вопрос

Итак, способ вложения исключений в C ++ с использованием std::nested_exception является:

void foo() {
  try {
    // code that might throw
    std::ifstream file("nonexistent.file");
    file.exceptions(std::ios_base::failbit);
  }

  catch(...) {
    std::throw_with_nested(std::runtime_error("foo failed"));
  }
}

Но этот метод использует явные блоки try / catch на каждом уровне, где требуется вложить исключения, что, мягко говоря, некрасиво.

RAII, который Джон Калб расширяется поскольку "получение ответственности - это инициализация", это гораздо более чистый способ обработки исключений вместо использования явных блоков try / catch.В RAII явные блоки try / catch в основном используются только для окончательной обработки исключения, напримердля того, чтобы отобразить пользователю сообщение об ошибке.

Глядя на приведенный выше код, мне кажется, что ввод foo() может рассматриваться как влекущий за собой ответственность сообщать о любых исключениях как std::runtime_error("foo failed") и вложите детали в nested_exception .Если мы сможем использовать RAII для получения этой ответственности, код будет выглядеть намного чище:

void foo() {
  Throw_with_nested on_error("foo failed");

  // code that might throw
  std::ifstream file("nonexistent.file");
  file.exceptions(std::ios_base::failbit);
}

Есть ли какой-либо способ использовать синтаксис RAII здесь для замены явных блоков try / catch?


Для этого нам нужен тип, который при вызове его деструктора проверяет, вызван ли вызов деструктора исключением, вкладывает это исключение, если это так, и выдает новое вложенное исключение, чтобы развертывание продолжалось нормально.Это может выглядеть как:

struct Throw_with_nested {
  const char *msg;

  Throw_with_nested(const char *error_message) : msg(error_message) {}

  ~Throw_with_nested() {
    if (std::uncaught_exception()) {
      std::throw_with_nested(std::runtime_error(msg));
    }
  }
};

Однако std::throw_with_nested() требуется, чтобы "обрабатываемое в данный момент исключение" было активным, что означает, что оно работает только внутри контекста блока catch.Итак, нам нужно что-то вроде:

  ~Throw_with_nested() {
    if (std::uncaught_exception()) {
      try {
        rethrow_uncaught_exception();
      }
      catch(...) {
        std::throw_with_nested(std::runtime_error(msg));
      }
    }
  }

К сожалению, насколько мне известно, нет ничего подобного rethrow_uncaught_excpetion() определено в C ++.

Это было полезно?

Решение

В отсутствие метода для перехвата (и использования) неперехваченного исключения в деструкторе невозможно повторно создать исключение, вложенное или нет, в контексте деструктора без std::terminate вызывается (когда исключение генерируется в контексте обработки исключений).

std::current_exception (в сочетании с std::rethrow_exception) вернет только указатель на обрабатываемое в данный момент исключение.Это исключает его использование в данном сценарии, поскольку исключение в данном случае явно необработано.

Учитывая вышесказанное, единственный ответ, который можно дать, - это с эстетической точки зрения.Блоки try на функциональном уровне делают это немного менее уродливым.(настройте в соответствии с вашими предпочтениями в стиле):

void foo() try {
  // code that might throw
  std::ifstream file("nonexistent.file");
  file.exceptions(std::ios_base::failbit);
}
catch(...) {
  std::throw_with_nested(std::runtime_error("foo failed"));
}

Другие советы

С RAII это невозможно

Учитывая простое правило

Деструкторы никогда не должны выбрасывать.

с RAII невозможно реализовать то, что вы хотите.У этого правила есть одна простая причина:Если деструктор генерирует исключение во время разматывания стека из-за исключения во время выполнения, то terminate() вызывается, и ваше приложение будет мертво.

Альтернатива

В C ++ 11 вы можете работать с лямбдами, что может немного облегчить жизнь.Вы можете написать

void foo()
{
    giveErrorContextOnFailure( "foo failed", [&]
    {
        // code that might throw
        std::ifstream file("nonexistent.file");
        file.exceptions(std::ios_base::failbit);
    } );
}

если вы реализуете функцию giveErrorContextOnFailure следующим образом:

template <typename F>
auto giveErrorContextOnFailure( const char * msg, F && f ) -> decltype(f())
{
    try { return f(); }
    catch { std::throw_with_nested(std::runtime_error(msg)); }
}

Это имеет несколько преимуществ:

  • Вы инкапсулируете, как ошибка является вложенной.
  • Изменение способа вложения ошибок может быть изменено для всей программы, если этот метод строго соблюдается во всей программе.
  • Сообщение об ошибке может быть записано перед кодом точно так же, как в RAII.Этот метод также может быть использован для вложенных областей.
  • Там меньше повторений кода:Тебе не обязательно писать try, catch, std::throw_with_nested и std::runtime_error.Это делает ваш код более удобным в обслуживании.Если вы хотите изменить поведение вашей программы, вам нужно изменить свой код только в одном месте.
  • Возвращаемый тип будет выведен автоматически.Итак, если ваша функция foo() должно что-то вернуть, тогда вы просто добавляете return до того , как giveErrorContextOnFailure в вашей функции foo().

В режиме выпуска обычно не будет панели производительности по сравнению с способом выполнения try-catch, поскольку шаблоны встроены по умолчанию.

Еще одно интересное правило, которому нужно следовать:

Не используйте std::uncaught_exception().

Там есть хороший статья об этом тема Херба Саттера, которая прекрасно объясняет это правило.Короче говоря:Если у вас есть функция f() который называется изнутри деструктора во время разматывания стека выглядящий вот так

void f()
{
    RAII r;
    bla();
}

где деструктор RAII выглядит как

RAII::~RAII()
{
    if ( std::uncaught_exception() )
    {
        // ...
    }
    else
    {
        // ...
    }
}

тогда всегда будет приниматься первая ветвь в деструкторе, так как во внешнем деструкторе во время разматывания стека std::uncaught_exception() всегда будет возвращать true, даже внутри функций, вызываемых из этого деструктора, включая деструктор RAII.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top