在关闭要写入的文件时检测错误很重要,因为数据的最后一部分可能在关闭期间被刷新,如果丢失则最后一次写入失败,有人应该知道它。文件对象的析构函数是一个自动关闭它的好地方,但人们说不要从析构函数中抛出异常,所以如果关闭失败那么你怎么知道呢?
我听说有人建议手动调用文件的close()方法。这听起来不错,除非在这种情况下多个文件的close()方法都失败了会发生什么:
MyFile x(0), y(1), z(2);
x.close();
y.close();
z.close();
好吧,似乎如果'x'的close()方法抛出异常,那么你已经做好了维护规则以避免在'x'的析构函数中抛出异常,除非你现在好了 - 意图早期调用'y'和'z'的close()方法直到它们的析构函数才会执行。那么,当在'y'的析构函数中调用'y'的close()方法或在'z'的析构函数中调用'z'的close()方法时,如果它们确实抛出异常,那么你'搞砸了。
有没有合理的方法可以在这种情况下搞砸?
答案 0 :(得分:5)
是的,捕获析构函数中close关闭的异常。
非常重要
是的,在这种情况下,通过捕获异常会丢失失败信息。如果用户实际关注错误,他们可以手动调用close并处理异常。
答案 1 :(得分:2)
这是一个常见问题项目:17.3 How can I handle a destructor that fails?
编辑:
好吧,好像,如果关闭() 'x'的方法然后抛出异常 你做得很好,坚持这个规则 避免抛出异常 'x'的析构函数,除了现在你 善意的早期打电话给 'y'和'z'的close()方法不会 执行直到他们的析构函数。
没有。如果您在y
部分周围安装了z
块,则当堆栈展开时,try-catch
和MyFile ... z.close()
的DORS将被称为。更好的想法是将close
放在dtor中。 x
的dtor不会 - 所以在catch
块中需要进行一些清理。
我建议你运行以下程序,以便在异常的情况下更好地理解dtor-calls(一旦原样,再次通过取消注释S x
行):
#include <iostream>
using namespace std;
struct s {
s(int i = 0) : m_i( i ) { cout << __func__ << endl; }
~s() { if (m_i == 0) except(); cout << __func__ << endl; }
void except() { throw 42; }
int m_i;
};
int main() {
try
{
s y(2), z(3);
/* s x */
y.except();
}
catch (...) { cout << "exception\n"; }
cout << "stack unwound\n";
}
答案 2 :(得分:1)
你不应该从析构函数中抛出 - 所以:
如果关闭有召唤异常的电话,我会吞下它们并执行以下操作之一:
选项1:写出错误消息并终止程序。
选项2:通过包装器对象(我可能会这样做)或全局变量或(可预见的)线程本地内存中的变量使错误可用。
选项1和2在这里看似合理。
使用选项2和包装器,您可以:
WrapFileX.close();
WrapFileY.close();
WrapFileZ.close();
if(WrapFileX.hasError || WrapFileY.hasError || WrapfileZ.hasError)
{ //log
exit(1)
}
答案 3 :(得分:1)
在这个例子中,我不明白为什么应该抛出任何。我认为这种情况不值得例外。理论上,关闭没有失败,它只是没有写出剩余的缓冲区;这不是一个非常特殊的情况。它可以被处理并且应该被处理,除非有理由需要关闭文件正确。
我个人来说,只有写入完成后才能使用close()函数块,然后继续关闭。
答案 4 :(得分:1)
我看到的规则是:
如果您不关心异常,那么让析构函数执行close工作(并捕获并丢弃异常)。如果您关心(并且可以对此做些什么),那么请手动关闭并捕获异常并执行相应的工作。
{
std::fstream X("Plop_X");
std::fstream Y("Plop_Y");
std::fstream Z("Plop_Z");
// Do work
try
{
X.close();
}
catch(Plop const& e)
{
// Fix the exception
// Then make sure X closes correctly.
// If we can't fix the problem
// rethrow
if (badJuJu)
{ throw;
}
}
// Don't care about Y/Z let the destructor close them and discard the exception.
}
答案 5 :(得分:0)
try {
x.close();
y.close();
z.close();
}
catch {
//do what ever you need to do here, then close() what' you'll need to close here
}
这就是我要做的,关键是你可能不知道哪一个抛出异常,哪一个留下来关闭。
答案 6 :(得分:0)
首先,MyFile的析构函数应该捕获异常(并且这是一个非常强大的“应该” - 它不是“必须”,因为如果它没有明确定义的行为,但它几乎不可取):
~MyFile() {
try {
close();
} catch(...) {}
// other cleanup
}
接下来,调用者应该决定是否要处理错误。如果他们不这样做,那么他们就可以让析构函数被调用。如果他们这样做,那么他们必须自己打电话。如果您的三个文件的示例,假设一旦其中一个文件失败,您不关心其他文件,您可以这样做:
MyFile x(0), y(1), z(2);
try {
x.close();
y.close();
z.close();
} catch(...) {
std::cerr << "something failed to close\n";
}
如果您想确切知道哪个失败,您需要确保实际调用所有三个关闭函数:
MyFile x(0), y(1), z(2);
try {
x.close();
} catch(...) {
std::cerr << "x failed to close\n";
}
try {
y.close();
} catch(...) {
std::cerr << "y failed to close\n";
}
try {
z.close();
} catch(...) {
std::cerr << "z failed to close\n";
}
当然,您可能希望对该代码进行一些共识。此外,如果您知道close
可以抛出的所有内容,那么您可以拥有比(...)
更好的catch子句。
这可能是标准库中的流的close()
函数不会引发异常的原因,除非用户设置异常掩码启用了该行为。