如果资源获取可能失败,如何实施RAII

时间:2011-12-19 18:05:42

标签: c++ raii

我想在RAII的帮助下实现一个课程。资源应该在构造函数中获取,但是acquistition可能会失败。我将在下面使用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。

我希望有人能给我一个如何处理这些错误的提示!

谢谢,最好的问候,

闪光

2 个答案:

答案 0 :(得分:6)

构造函数报告失败的唯一方法是抛出异常。

相反,析构函数不能抛出异常(如果在堆栈展开期间抛出析构函数,则会调用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函数可能会失败,并且失败可能与关闭有关,也可能与关闭无关(例如,某些写错误仅在{{1}时刷新时报告系统调用POSIX系统)。请使用iostreams在C ++中进行文件I / O,因为它们提供了对这些问题的方便抽象。

答案 1 :(得分:1)

尽管有这个名字,RAII并不一定涉及资源收购; 例如,std::shared_ptr不会执行new。名字是 历史,这种模式确实涉及清理。

File的情况下,当然,“正确”的答案是使用 std::ifstreamstd::ofstream,并完成它。 非常 重要的(至少对于输出):RAII只能用于例外 清理,因为您必须验证close是否正确 在正常情况下离开街区前完成。作为一般 规则,如果关闭失败,您要删除该文件(并返回 来自EXIT_FAILURE的{​​{1}},或做出会导致错误的内容 显而易见,并防止进一步的代码在假设下执行 数据是写的)。所以唯一可以接受的时间 将main推迟到析构函数是在你已经存在的情况下 发现错误,并将作为清理的一部分删除该文件 反正。

通常,输出服从交易模型,而不是RAII;一世 倾向于将我的close包裹在std::ofstream课程中 OutputFile函数关闭文件,并将类标记为 如果(并且仅当)结束成功,则承诺;如果析构函数是 调用并且文件尚未提交,析构函数关闭了 文件,然后删除它。 (据推测,一些更高级别的人会抓住 异常并将其转换为main中的commit()。)

此外,IO总的来说有点特殊。通常,如果你不能 创建一个对象,无论出于什么原因,构造函数应该引发一个 例外;你不希望“僵尸”物体漂浮在周围,这不可能 使用。但是,对于IO,您必须处理这个事实 即使是在施工后物体也会变得无效和无法使用 施工成功的时候。因为你必须经常检查他们的 无论如何状态(每次读取输入后,输出结束后), 通常在对象中设置一些内部标志是合适的, 你测试的。