如何处理抛出异常的文件析构函数?

时间:2010-02-01 15:47:27

标签: c++

在关闭要写入的文件时检测错误很重要,因为数据的最后一部分可能在关闭期间被刷新,如果丢失则最后一次写入失败,有人应该知道它。文件对象的析构函数是一个自动关闭它的好地方,但人们说不要从析构函数中抛出异常,所以如果关闭失败那么你怎么知道呢?

我听说有人建议手动调用文件的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()方法时,如果它们确实抛出异常,那么你'搞砸了。

有没有合理的方法可以在这种情况下搞砸?

7 个答案:

答案 0 :(得分:5)

是的,捕获析构函数中close关闭的异常。

非常重要析构函数不会抛出异常期。否则将破坏几乎每个可用库中的许多资源管理例程。

是的,在这种情况下,通过捕获异常会丢失失败信息。如果用户实际关注错误,他们可以手动调用close并处理异常。

答案 1 :(得分:2)

这是一个常见问题项目:17.3 How can I handle a destructor that fails?

编辑:

  好吧,好像,如果关闭()   'x'的方法然后抛出异常   你做得很好,坚持这个规则   避免抛出异常   'x'的析构函数,除了现在你   善意的早期打电话给   'y'和'z'的close()方法不会   执行直到他们的析构函数。

没有。如果您在y部分周围安装了z块,则当堆栈展开时,try-catchMyFile ... 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()函数不会引发异常的原因,除非用户设置异常掩码启用了该行为。