RAII способ получить ошибки, обнаруженные во время уничтожения

В типичном примере RAII для файлового ввода-вывода в Википедии любые ошибки, возникающие при закрытии файла, проглатываются:

#include <iostream>
#include <string> 
#include <fstream>
#include <stdexcept>

void write_to_file (const std::string & message) {
    // try to open file
    std::ofstream file("example.txt");
    if (!file.is_open())
        throw std::runtime_error("unable to open file");

    // write message to file
    file << message << std::endl;

    // file will be closed when leaving scope (regardless of exception)
}

Кажется, нет способа определить, произошла ли ошибка при автоматическом закрытии file; очевидно, можно вызывать только file.rdstate(), пока file находится в области видимости.

Я мог бы вызвать file.close() вручную, а затем проверить наличие ошибки, но мне пришлось бы делать это в каждом месте, где я возвращаюсь из области видимости, что противоречит цели RAII.

Некоторые отмечают, что в деструкторе могут возникать только неустранимые ошибки, такие как повреждение файловой системы, но я не верю, что это правда, потому что AFAIK деструктор очищает файл перед его закрытием, и во время очистки могут возникать исправимые ошибки.

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

Самый простой способ, который я могу придумать, — это зарегистрировать функцию обратного вызова, которую деструктор будет вызывать, если во время уничтожения возникнут какие-либо ошибки. Удивительно, но не похоже, что для этого существует событие, поддерживаемое ios_base::register_callback. Это похоже на серьезную оплошность, если я что-то не так понял.

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

Я предполагаю, что вызов произвольной функции в деструкторе также опасен, но, возможно, оборачивать вызов в блок try/catch совершенно безопасно.


person Andy    schedule 20.02.2018    source источник
comment
У вас есть классическая проблема XY. Невозможность закрыть файл часто означает неисправимую ошибку, то есть повреждение файловой системы. Как бы вы справились с этой ошибкой или восстановились после нее? Логично было бы просто отправить исключение в глобальный регистратор.   -  person user167921    schedule 20.02.2018
comment
Какое разрушение? У вас здесь нет класса. А мьютекс для чего?   -  person Jive Dadson    schedule 20.02.2018
comment
@user167921 user167921 в этом случае я хочу, по крайней мере, показать пользователю диалоговое окно с сообщением об ошибке, в котором говорится, что что-то пошло не так при записи в файл.   -  person Andy    schedule 20.02.2018
comment
@JiveDadson хорошо, я избавился от мьютекса из кода, который я скопировал, так как он не имеет значения. Нет, у меня здесь нет собственного класса, но std::ofstream закрывает свой файл при уничтожении, и я спрашиваю, как бы я обработал ошибку закрытия файла, если бы сам разработал аналогичный класс.   -  person Andy    schedule 20.02.2018
comment
@user167921 user167921 также, все ли данные, записанные в file, гарантированно будут очищены к моменту вызова его деструктора? Потому что, если нет, я предполагаю, что деструктор также очищает вывод перед закрытием файла, во время которого может возникнуть исправимая ошибка.   -  person Andy    schedule 20.02.2018
comment
Вы можете создать класс-оболочку, который перенаправляет необходимые операторы/члены в содержащийся файл записи (тот, который вы не можете контролировать). Затем, когда оболочка уничтожена, вы можете проверить, не произошла ли ошибка при закрытии файла, и действовать соответствующим образом. RAII через RAII   -  person amc176    schedule 20.02.2018
comment
@ user167921 в соответствии с stackoverflow.com/questions/3113229/ofstream-doesnt-flush, деструктор должен очищаться, поэтому во время деструктора могут возникать исправимые ошибки ввода-вывода.   -  person Andy    schedule 20.02.2018
comment
@ amc176 amc176 это хорошая идея, если мне нужно проверить наличие ошибок хотя бы на самом ofstream, спасибо!   -  person Andy    schedule 20.02.2018
comment
Я предполагаю, что эта моя часть будет актуальна: деструктор">stackoverflow.com/questions/130117/   -  person Martin Ba    schedule 20.02.2018
comment
Файловые потоки используют std::basic_filebuf<...>, деструктор вызывает close, который действительно очищает буфер.   -  person Some programmer dude    schedule 20.02.2018


Ответы (2)


Вы можете частично обработать случай сбоя в деструкторе:

class Foo {
public:
    Foo() : count(std::uncaught_exceptions()) {}
    ~Foo() noexcept(false)
    {
        if (std::uncaught_exceptions() != count) {
            // ~Foo() called during stack unwinding
            // Cannot throw exception safely.
        } else {
            // ~Foo() called normally
            // Can throw exception
        }
    }
private:
    int count;
};
person Jarod42    schedule 20.02.2018
comment
Интересно, почему в языке нет функции автоматического проглатывания исключений, генерируемых деструктором во время раскручивания стека? - person Andy; 20.02.2018
comment
Еще в C++98 мы не могли реализовать этот условный бросок d'tor (кроме доступа к внутренним компонентам платформы). Теперь мы можем, но должны ли? См. этот раздел GotW Неправильное решение: почему подход аморален :-) - person Arne Vogel; 20.02.2018
comment
@Andy: Интересно, почему у нас нет возможности сбросить std::nested_exception из раскручивания :-/ - person Jarod42; 20.02.2018
comment
@ArneVogel: std::uncaught_exceptions() позволяет по-настоящему справиться с делом, в отличие от старого std::uncaught_exception(). Но я смягчаю безнравственность: автоматическая фиксация/откат выглядит неплохо. Его правильное решение в основном состоит в том, чтобы не использовать RAII (вызов Close() вручную) :-( Печально, что мы не можем отправить несколько исключений... - person Jarod42; 20.02.2018

Если у вас есть специальный код, обрабатывающий любые ошибки закрытия файла при раскрутке, вы можете добавить еще один уровень абстракции...

class MyFileHandler {
    std::ofstream& f_;
public:
    MyFileHandler(std::ofstream& f) : f_(f) {}
    ~MyFileHandler() {
        f_.close();
        // handle errors as required...
    }
    // copy and assignments elided for brevity, but should well be deleted
};

void write_to_file (const std::string & message) {
    // try to open file
    std::ofstream file("example.txt");
    MyFileHandler fileCloser(file);

    if (!file.is_open())
        throw std::runtime_error("unable to open file");

    // write message to file
    file << message << std::endl;

    // file will be closed when leaving scope (regardless of exception)
}

В зависимости от вашего варианта использования вы можете встроить std::ofstream в класс.

person Niall    schedule 20.02.2018