【问题标题】:How to handle a file destructor throwing an exception?如何处理抛出异常的文件析构函数?
【发布时间】:2010-02-01 15:47:27
【问题描述】:

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

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

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

【问题讨论】:

  • 看起来可能是C++,相应标记
  • 我没有看到这里的问题。析构函数必然包含对 close 的调用,必须由 try-catch 包围。无法通过异常、句号来处理析构函数中的失败。

标签: c++


【解决方案1】:

是的,在析构函数中捕获 close 抛出的异常。

C++ 析构函数不抛出异常周期是至关重要的。否则会弄乱几乎每个可用库中的许多资源管理例程。

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

【讨论】:

    【解决方案2】:

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

    编辑:

    好吧,似乎如果 close() 'x' 的方法然后抛出异常 你很好地维护了规则 避免在 'x' 的析构函数,除了现在你是 善意的早期呼吁 'y' 和 'z' 的 close() 方法不会 执行直到它们的析构函数。

    没有。 yz 的 Dtor 将在堆栈展开时调用,前提是您在 MyFile ... z.close() 部分周围安装了 try-catch 块。一个更好的主意是将close 也放入 dtor 中。 x 的 dtor 不会——因此在 catch 块中需要进行一些清理。

    我建议您运行以下程序以更好地理解异常情况下的 dtor 调用(一次按原样,然后通过取消注释 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";
    }
    

    【讨论】:

      【解决方案3】:

      你不应该从析构函数中抛出——所以:

      如果 close 有引发异常的调用,我会吞下它们并执行以下操作之一:

      选项 1:写出错误消息并终止程序。

      选项 2:通过包装对象(我可能会这样做)或全局变量或(最好)线程本地内存中的变量使错误可用。

      选项 1 和 2 在这里看起来都很合理。

      使用选项 2 和你会做的包装器:

      WrapFileX.close();
      WrapFileY.close();
      WrapFileZ.close();
      
      if(WrapFileX.hasError || WrapFileY.hasError || WrapfileZ.hasError)
      {   //log
          exit(1)
      }
      

      【讨论】:

        【解决方案4】:

        在这个例子中,我不明白为什么应该抛出 anything。我认为这种情况根本不值得例外。理论上,关闭并没有失败,只是没有写入缓冲区的其余部分;这不是一个非常特殊的情况。它可以被处理并且应该被处理,除非有理由需要立即关闭文件

        就我个人而言,我的 close() 函数块会一直到写入完成,然后继续关闭。

        【讨论】:

        • flush/close 也可能永久失败,例如,如果底层文件系统空间不足,或者底层套接字或管道已断开连接或损坏。不影响您的主要观点,不需要异常,但您不能等到它起作用。
        【解决方案5】:

        我看到的规则是:

        如果您不关心异常,则让析构函数完成关闭工作(并捕获并丢弃异常)。如果您确实关心(并且可以对此做一些事情),请手动关闭并捕获异常并执行相应的工作。

        {
            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.
        }
        

        【讨论】:

          【解决方案6】:
          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
          }
          

          这就是我会做的,关键是你可能不知道哪个抛出异常,哪个留下来关闭。

          【讨论】:

            【解决方案7】:

            首先,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() 函数不抛出异常的原因,除非该行为已由设置异常掩码的用户启用。

            【讨论】:

              猜你喜欢
              • 2020-02-24
              • 2015-08-26
              • 1970-01-01
              • 1970-01-01
              • 2022-01-16
              • 1970-01-01
              • 2021-05-27
              相关资源
              最近更新 更多