C ++ с использованием RAII с деструктором, который выбрасывает

Скажем, у меня есть класс 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 всегда является объектом, размещенным в стеке, и это не общая проблема деструктора.


person gsf    schedule 02.04.2016    source источник
comment
На самом деле это не отличается от более общего случая того, как предотвратить завершение, если у меня есть класс, деструктор которого может вызывать. Я не думаю, что есть хороший ответ, кроме того, что деструкторы не должны не бросать.   -  person sfjac    schedule 02.04.2016
comment
Без деструктора, выполняющего некоторую работу, RAII не существует. Я очень надеюсь, что есть что-то, что можно сделать. Скажите что-нибудь на основе std :: uncaught_exception, где вы можете определить, например, есть ли в процессе исключения.   -  person gsf    schedule 02.04.2016


Ответы (2)


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

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

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

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

person Yakk - Adam Nevraumont    schedule 02.04.2016
comment
@kerrek доработан ли C ++ 17? - person Yakk - Adam Nevraumont; 02.04.2016
comment
Соответствующий документ был проголосован и применен (N4582), и практически нет шансов, что он будет удален, теперь, когда WD считается полнофункциональным для 17. - person Kerrek SB; 02.04.2016

Совет из статьи ScopeGuard был

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

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

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

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

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


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

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

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

person Max Lybbert    schedule 02.04.2016