2016-04-02 3 views
6

Пусть у меня есть RAII класс:C++ с использованием RAII с деструктора, генерирующий

class Raii { 
    Raii() {}; 
    ~Raii() { 
     if (<something>) throw std::exception(); 
    } 
}; 

И если у меня есть функция:

void foo() { 
    Raii raii;  

    if (something) { 
     throw std::exception(); 
    } 
} 

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

Мой вопрос: что такое хороший шаблон для использования raii для кода, который может быть выбрана очисткой?

Например, это хорошо или плохо - почему?

class Raii { 
    Raii() {}; 
    ~Raii() { 
     try { 
      if (<something>) throw std::exception(); 
     } 
     catch (...) { 
      if (!std::uncaught_exception()) 
       throw; 
     } 
    } 
}; 

Обратите внимание, что объект Raii всегда стек выделяется объект - и это не является общим броском от проблемы деструктора.

+8

Это на самом деле не отличается от более общего случая «как предотвратить прерывание, если у меня есть класс, деструктор которого может бросить». Я не думаю, что есть хороший ответ, кроме как «деструкторы должны * не бросать». – sfjac

+1

Без деструктора, выполняющего некоторую работу, нет RAII. Я очень надеюсь, что есть что-то, что можно было бы сделать. Скажите что-то, основанное на std :: uncaught_exception, где вы можете обнаружить, если есть, например, какое-то исключение. – gsf

ответ

7

У C++ почти наверняка есть функция для получения текущего количества исключений из C++ 1z (например, C++ 17, если они публикуют его вовремя!): std::uncaught_exceptions (обратите внимание на множественное число «s»). Кроме того, деструкторы объявляются как noexcept по умолчанию (это означает, что если вы попытаетесь выйти из деструктора с помощью исключения, вызывается std::terminate).

Итак, отметьте свой деструктор как метательный (noexcept(false)). Затем отслеживайте количество активных исключений в ctor, сравнивайте их со значением в dtor: если в dtor есть больше исключенных исключений, вы знаете, что в настоящее время вы находитесь в процессе разворачивания стека, а повторное нажатие приведет к вызову до std::terminate.

Теперь вы точно определяете, насколько вы исключительны, и как вы хотите справиться с ситуацией: прекратите выполнение программы или просто проглотите внутреннее исключение?

Плохая имитация, чтобы не бросать, если uncaught_exception (в единственном числе) возвращает истину, но делает исключение не работает при вызове из другого dtor спровоцировал от разматывания, который пытается поймать и процесс ваших исключения. Эта опция доступна в текущих стандартах C++.

+0

@kerrek имеет C++ 17 завершена? – Yakk

+0

Соответствующая статья была проголосована и применена (N4582), и практически нет никаких шансов, что она будет снята, теперь, когда WD считается «полным набором функций» для 17. –

4

Советы от the ScopeGuard article был

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

Это может показаться сумасшедшим, но учтите:

  1. мне удается запустить из памяти и получить std::bad_alloc исключение
  2. Моя очистка код регистрирует ошибку
  3. К сожалению, запись (возможно, диск заполнен) и пытается выбросить исключение

Можно ли отменить запись журнала? Должен ли я попробовать?

Когда вы выбрали исключение, все, что вы действительно знаете, это то, что программа находится в недопустимом состоянии. Вы не должны удивляться, что некоторые невозможные вещи оказываются возможными в конце концов.Лично я видел гораздо больше случаев, когда советы Александрадреску имеют наибольший смысл, чем в противном случае: попытайтесь очистить, но признайте, что первое исключение означает, что вещи уже находятся в недопустимом состоянии, поэтому дополнительные сбои - особенно сбои, вызванные первая проблема («каскад ошибок») - не должно быть неожиданностью. И попытка справиться с ними не закончится хорошо.


я, вероятно, следует отметить, что Cap'n Прото делает именно то, что вы предложили:

Когда Cap'n Прото код может бросить исключение из деструктора, он сначала проверяет std::uncaught_exception(), чтобы обеспечить что это безопасно. Если другое исключение уже активно, новое исключение считается побочным эффектом основного исключения и либо молчаливо проглатывается, либо сообщается на боковом канале.

Но, как сказал Якк, деструкторы стали nothrow(true) по умолчанию в C++ 11. Это означает, что если вы хотите это сделать, вы должны быть уверены, что в C++ 11 и более поздних версиях вы помечаете деструктор как nothrow(false). В противном случае исключение из деструктора исключает программу, даже если в полете нет другого исключения. И обратите внимание: «Если другое исключение уже активно, новое исключение считается побочным эффектом основного исключения и либо молчаливо проглатывается, либо сообщается на боковом канале».