Обнаружение активных исключений в деструкторе


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

Foo::~Foo () {
  try { /* do not know what to put here */ }
  catch( const std::exception & ex ) {
     // produce error in log
     throw;
  }
  catch( ... ) {
    // produce some other log message
    throw;
   }
}
Однако я не уверен, что это вообще сработает, так как исключение активно уже до вызова деструктора и не происходит из блока try. Кроме того, я использую throw; внутри деструктора и бросать исключение в этот момент-действительно плохая идея. Поэтому я не буду этого делать, если только стандарт не гарантирует ясно, что этот случай является исключением (без каламбура) из этого правила (которого я не знаю).

Так возможно ли это вообще, или я должен справиться с этой ситуацией каким-то другим способом?

3 8

3 ответа:

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

Обратите внимание, хорошие рекомендации по программированию обычно диктуют, что если ваш деструктор ведет себя значительно по-разному в разных обстоятельствах, это обычно не очень хорошая идея. Поэтому используйте эту функцию, но не злоупотребляйте ею. Общее применение-иметь деструктор, который только бросает, если нет активного исключения uncaught (эта функция возвращает false). Но такой тип поведения, как правило, не является хорошим дизайном. Если состояние было настолько плохим, что требовало исключения, его, вероятно, не стоило игнорировать. И деструкторы не должны выбрасывать исключения в любом случае.

Пример:

Foo::~Foo()
{
    if (std::uncaught_exception()) 
    {
        std::cerr << "Warning: Can't cleanup properly" << std::endl;
        return;
    }
    else
    {
        // ...
    }
}

Это невозможно сделать безопасным способом.

Что вы можете сделать, так это проверить ваши параметры в деструкторе, и это должно сказать, была ли обработка завершена.

Кстати, почему вы не регистрируете ошибку в блоке catch, где вы действительно можете обработать ошибку? Там вы должны знать, что обработка была завершена с ошибкой.

Взгляните на этот мой вопрос и ответы на него.

В принципе, вы можете перестроить текущее активное исключение, используя простой throw в первом блоке try. Однако это безопасно только в том случае, если вы точно знаете, что в данный момент обрабатывается исключение, т. е. деструктор вызывается из блока catch. в противном случае throw вызовет std::terminate().

Foo::~Foo () {
  try {  throw; } // only if you *know* that an exception is active - bad thing for a generic destructor
  catch( const std::exception & ex ) {
     // produce error in log

  }
  catch( ... ) {
    // produce some other log message

   }
}

Однако выбрасывание исключений из деструкторов не одобряется, поэтому я бы не стал затем повторно сгенерировать исключение. В конце концов, все это не кажется мне хорошей идеей.

Шаблон обработки исключений хорош, но я бы не стал делать это в несвязанном деструкторе. Опять же, смотрите упомянутый вопрос.