尽管其他答案是有效且有用的,但我认为real原因更简单。
iostreams 的设计比许多标准库要古老得多,并且早于异常的广泛使用。我怀疑为了与现有代码兼容,异常的使用是可选的,而不是打开文件失败的默认值。
另外,您的问题仅与文件流真正相关,其他类型的标准流没有open()
or close()
成员函数,因此如果无法打开文件,它们的构造函数不会抛出异常:-)
对于文件,您可能需要检查close()
调用成功,因此您知道数据是否已写入磁盘,这是一个很好的理由not在析构函数中执行此操作,因为当对象被销毁时,已经太晚了,无法对其执行任何有用的操作,并且您几乎肯定不想从析构函数中抛出异常。所以一个fstreambuf
将在其析构函数中调用 close,但如果您愿意,您也可以在销毁之前手动执行此操作。
无论如何,我不同意它不遵循 RAII 约定......
为什么库设计者选择他们的方法而不是仅在引发失败的构造函数中打开?
注意: RAII 并不代表你can't有一个单独的open()
除了获取资源的构造函数之外的成员,或者您can't在销毁之前清理资源,例如unique_ptr
has a reset()
member.
另外,RAII并不意味着你must失败时抛出,或者抛出一个对象can't处于空状态,例如unique_ptr
可以使用空指针或默认构造来构造,因此也可以指向任何内容,因此在某些情况下您需要在取消引用之前检查它。
文件流在构建时获取资源并在销毁时释放它 - 就我而言,这就是 RAII。您反对的是需要进行检查,这有两阶段初始化的味道,我同意这有点臭。但这并不意味着它不是 RAII。
过去我用过一种方法来解决气味CheckedFstream
类,这是一个简单的包装器,添加了一个功能:抛出无法打开流的构造函数。在 C++11 中,就这么简单:
struct CheckedFstream : std::fstream
{
CheckedFstream() = default;
CheckedFstream(std::string const& path, std::ios::openmode m = std::ios::in|std::ios::out)
: fstream(path, m)
{ if (!is_open()) throw std::ios::failure("Could not open " + path); }
};