Как реализовать RAII, если получение ресурса может завершиться неудачей - PullRequest
1 голос
/ 19 декабря 2011

Я хотел бы реализовать класс с помощью RAII.Ресурсы должны быть получены в конструкторе, но возможно, что получение не удалось.Ниже приведу пример использования FILE:

class file {
public:
    file(const char* filename) {
        file_ = fopen(filename, "w+");
        if(!file_) {
          // Okay
        }
        else {
          // ERROR
        }
    }

    ~file() {
        if (fclose(file_)) {
           // ERROR
        }
    }

    void write(const char* str) {
        if (EOF == fputs(str, file_)) {
            throw runtime_error("file write failure");
        }
    }
private:
    FILE* file_;
};

Итак, как лучше всего обработать ошибку, которая возникает, если fopen возвращает NULL?Поскольку это конструктор, я не могу вернуть также NULL.

Я надеюсь, что кто-нибудь подскажет мне, как обрабатывать такие ошибки!

Спасибо, с наилучшими пожеланиями,

Flasher

Ответы [ 2 ]

6 голосов
/ 19 декабря 2011

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

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

Если уничтожение не удалось, вы можете

  • Глотать ошибки в молчании
  • Прервать программу
  • Записать ошибку и выполнитьлюбой из вышеперечисленных.

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

Пример здесь:

#include <cerrno>
#include <cstring>
#include <sstream>

file::file(const char* filename) 
{
    file_ = fopen(filename, "w+");

    if (!file_)
    {
        std::ostringstream os;
        os << "Cannot open " << filename << ": "
           << std::strerror(errno);

        throw std::runtime_error(os.str());
    }
}

file::~file()
{
    fclose(file_);
}

Обратите внимание, что этот кодимеет много ошибок: функция fclose может давать сбой, и сбой может или не может быть связан с закрытием (например, некоторые ошибки записи сообщаются только при сбросе при системном вызове close в системах POSIX).Пожалуйста, используйте iostreams для файлового ввода-вывода в C ++, поскольку они предоставляют удобные абстракции по этим вопросам.

1 голос
/ 19 декабря 2011

Несмотря на название, RAII не обязательно включает в себя приобретение ресурсов; Например, std::shared_ptr не делает new. Имя исторический, и картина действительно касается очистки.

В случае File, конечно, «правильным» ответом является использование std::ifstream и std::ofstream, и покончим с этим. А очень важно (по крайней мере, для вывода): RAII может использоваться только для исключительных очистка, так как вы должны убедиться, что close правильно закончил, прежде чем покинуть блок в обычном случае. В целом правило, если закрыть не удается, вы хотите удалить файл (и вернуть EXIT_FAILURE из main, или сделайте что-нибудь, что сделает ошибку и препятствовать выполнению следующего кода, исходя из предположения, что данные были написаны). Так что единственный раз, когда это было бы приемлемо для отложить close на деструктор в случае, когда вы уже обнаружил ошибку и собираюсь удалить файл как часть очистки в любом случае.

Как правило, выходные данные подчиняются модели транзакций, более чем RAII; я как правило, обернуть мой std::ofstream в классе OutputFile, с commit() функция, которая закрывает файл и помечает класс как совершено, если (и только если) успешное закрытие; если деструктор вызван, и файл не был передан, деструктор закрывает файл, а затем удаляет его. (Предположительно какой-то более высокий уровень будет ловить исключение и преобразовать его в return EXIT_FAILURE в основном.)

Кроме того, IO в целом немного исключительный. Обычно, если вы не можете создать объект, по какой-либо причине, конструктор должен поднять исключение; Вы не хотите, чтобы объекты "зомби" плавали вокруг, что не может использоваться. В случае IO, однако, вы должны иметь дело с тем, что объект может стать недействительным и непригодным после строительства, даже когда строительство удастся. Так как вы должны постоянно проверять их в любом случае (после каждого чтения на входе и после закрытия на выходе), часто уместно просто установить некоторый внутренний флаг в объекте, который вы тестируете.

...