Jef*_*eff 28 c++ iostream raii c++-standard-library
为什么C++标准库流使用open()/ close()语义与对象生命周期分离?关闭破坏可能仍然在技术上使类RAII,但获取/释放独立性在范围中留下漏洞,其中句柄可以指向什么,但仍然需要运行时检查来捕获.
为什么图书馆设计师选择他们的方法而不是只在构造失败的构造函数中打开?
void foo() {
std::ofstream ofs;
ofs << "Can't do this!\n"; // 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!\n"; // 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 ...
}
Run Code Online (Sandbox Code Playgroud)
这个问题的更好的措辞可能是为什么访问非开放fstream是值得拥有的.通过句柄生命周期控制打开文件持续时间在我看来根本不是负担,但实际上是一个安全上的好处.
Jon*_*ely 33
虽然其他答案是有效和有用的,但我认为真正的原因更简单.
iostreams设计比许多标准库要早得多,并且在广泛使用异常之前.我怀疑为了与现有代码兼容,异常的使用是可选的,而不是打开文件失败的默认值.
此外,您的问题只与文件流真正相关,其他类型的标准流没有open()或close()成员函数,因此如果无法打开文件,它们的构造函数不会抛出:-)
对于文件,您可能需要检查close()调用是否成功,因此您知道数据是否已写入磁盘,因此这是不在析构函数中执行此操作的充分理由,因为在对象被销毁时,为时已晚做任何有用的事情,你几乎肯定不想从析构函数中抛出异常.因此,fstreambuf将在析构函数中调用close,但如果您愿意,也可以在销毁之前手动执行.
无论如何,我不同意它不符合RAII惯例......
为什么图书馆设计师选择他们的方法而不是只在构造失败的构造函数中打开?
NB RAII并不意味着除了资源获取构造函数之外您不能拥有单独的open()成员,或者您无法在破坏之前清理资源,例如unique_ptr拥有reset()成员.
另外,RAII并不意味着你必须抛出失败,或者一个对象不能处于空状态,例如unique_ptr可以用空指针构造或默认构造,因此也可以指向任何东西,所以在某些情况下你在解除引用之前需要检查它.
文件流获取构建资源并在销毁时释放它 - 就我而言,这就是RAII.你反对的是需要检查,这有两个阶段的初始化,我同意这有点臭.但它不会使它不是RAII.
在过去,我用CheckedFstream类来解决气味,这是一个简单的包装器,它添加了一个功能:如果无法打开流,则抛出cosntructor.在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); }
};
Run Code Online (Sandbox Code Playgroud)
bol*_*lov 14
通过这种方式,您可以获得更多,更少.
你得到相同的:你仍然可以通过构造函数打开文件.你仍然得到RAII:它将在对象销毁时自动关闭文件.
你得到更多:你可以使用相同的流重新打开其他文件; 您可以在需要时关闭文件,而不是限制等待对象超出范围或被破坏(这非常重要).
你得到的更少:你看到的优势并不真实.你说你的方式你不必检查每个操作.这是错误的.即使成功打开(文件),流也可能随时失败.
关于错误检查与抛出异常,请参阅@ PiotrS的回答.从概念上讲,我认为必须检查返回状态与必须捕获错误之间没有区别.错误仍然存在; 区别在于你如何检测它.但正如@PiotrS指出的那样,你可以选择两者.
图书馆设计师给了你另类选择:
std::ifstream file{};
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try
{
file.open(path); // now it will throw on failure
}
catch (const std::ifstream::failure& e)
{
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3950 次 |
| 最近记录: |