为什么C++标准文件流不能更严格地遵循RAII约定?
为什么C++标准库流使用与对象生存期分离的
open()
/close()
语义?从技术上讲,关闭销毁可能仍然会使类成为RAII类,但获取/释放独立性会在作用域中留下漏洞,其中句柄可以什么都不指向,但仍然需要运行时检查才能捕获。
为什么库设计者选择他们的方法,而不是只在引发失败的构造函数中打开?
void foo() {
std::ofstream ofs;
ofs << "Can't do this!
"; // XXX
ofs.open("foo.txt");
// Safe access requires explicit checking after open().
if (ofs) {
// Other calls still need checks but must be shielded by an initial one.
}
ofs.close();
ofs << "Whoops!
"; // XXX
}
// This approach would seem better IMO:
void bar() {
std_raii::ofstream ofs("foo.txt"); // throw on failure and catch wherever
// do whatever, then close ofs on destruction ...
}
这个问题的一个更好的措辞可能是,为什么访问未打开的fstream
值得拥有。在我看来,通过句柄生存期控制打开文件的持续时间并不是一种负担,而实际上是一种安全益处。
解决方案
虽然其他答案有效且有用,但我认为实际原因更简单。
IOSTREAMS设计比许多标准库都要老得多,而且早于异常的广泛使用。我怀疑,为了与现有代码兼容,使用异常是可选的,而不是无法打开文件时的默认设置。
另外,您的问题实际上只与文件流相关,其他类型的标准流没有open()
或close()
成员函数,所以如果文件无法打开,它们的构造函数不会抛出:-)
对于文件,您可能希望检查close()
调用是否成功,这样您就知道数据是否已写入磁盘,因此不在析构函数中执行此操作是一个很好的理由,因为当对象被销毁时,对其执行任何有用的操作都为时已晚,而且您几乎肯定不想从析构函数引发异常。因此,fstreambuf
将在其析构函数中调用Close,但如果您愿意,也可以在销毁之前手动执行此操作。
无论如何,我不同意它不遵循RAII惯例...
为什么库设计者选择他们的方法,而不是只在引发失败的构造函数中打开?
N.B.RAII并不意味着除了资源获取构造函数之外不能拥有单独的open()
成员,或者不能在销毁前清除资源,例如unique_ptr
有reset()
成员。
此外,RAII并不意味着必须在失败时抛出,或者对象不能处于空状态,例如,unique_ptr
可以用空指针构造或默认构造,因此也可以指向任何东西,因此在某些情况下,您需要在取消引用之前检查它。
CheckedFstream
类解决了这个问题,这个类是一个简单的包装器,它添加了一个功能:如果流无法打开,则抛出contrator。在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); }
};
相关文章