RAII与例外 我们在C++中使用RAII越多,我们发现析构函数就越做越小。现在,解除分配(finalization,不管你怎么称呼它)可能会失败,在这种情况下,异常是让楼上任何人知道我们的解除分配问题的唯一方法。但是,同样,抛出析构函数是一个坏主意,因为在堆栈展开期间可能会抛出异常std::uncaught_exception()让您知道什么时候会发生这种情况,但不会更多,因此除了让您在终止前记录消息之外,您也无能为力,除非您愿意让程序处于未定义的状态,其中一些内容被释放/完成,而另一些则没有

RAII与例外 我们在C++中使用RAII越多,我们发现析构函数就越做越小。现在,解除分配(finalization,不管你怎么称呼它)可能会失败,在这种情况下,异常是让楼上任何人知道我们的解除分配问题的唯一方法。但是,同样,抛出析构函数是一个坏主意,因为在堆栈展开期间可能会抛出异常std::uncaught_exception()让您知道什么时候会发生这种情况,但不会更多,因此除了让您在终止前记录消息之外,您也无能为力,除非您愿意让程序处于未定义的状态,其中一些内容被释放/完成,而另一些则没有,c++,exception,raii,destructor,C++,Exception,Raii,Destructor,一种方法是不使用抛出析构函数。但在许多情况下,这只是隐藏了一个真正的错误。例如,我们的析构函数可能由于抛出了一些异常而关闭一些RAII管理的DB连接,而这些DB连接可能无法关闭。这并不一定意味着我们可以在此时终止程序。另一方面,记录和跟踪这些错误并不是所有情况下的解决方案;否则我们就没有必要从例外开始。 在没有抛出析构函数的情况下,我们还发现自己必须创建“reset()”函数,这些函数应该在销毁之前被调用——但这恰恰违背了RAII的全部目的 另一种方法是直接去做,因为这是你能做的最可预测的事情

一种方法是不使用抛出析构函数。但在许多情况下,这只是隐藏了一个真正的错误。例如,我们的析构函数可能由于抛出了一些异常而关闭一些RAII管理的DB连接,而这些DB连接可能无法关闭。这并不一定意味着我们可以在此时终止程序。另一方面,记录和跟踪这些错误并不是所有情况下的解决方案;否则我们就没有必要从例外开始。 在没有抛出析构函数的情况下,我们还发现自己必须创建“reset()”函数,这些函数应该在销毁之前被调用——但这恰恰违背了RAII的全部目的

另一种方法是直接去做,因为这是你能做的最可预测的事情

有些人建议链接异常,以便一次可以处理多个错误。但我真的从来没有真正看到过C++中所做的,我不知道如何实现这样的事情。 所以不是RAII就是例外。不是吗?我倾向于不扔破坏者;主要是因为它使事情变得简单(r)。但我真的希望有一个更好的解决方案,因为正如我所说的,我们使用RAII越多,我们发现自己使用的DTR做的事情就越多

附录

我正在添加我发现的有趣的主题文章和讨论的链接:

  • StackOverflow算法探讨
  • StackOverflow讨论(谢谢,Martin York)
  • 这也涉及异常链接
  • 为什么它没有你想象的那么有用
  • 与有趣的参与者(长!)
  • 斯特罗斯特鲁普解释
  • 安德烈·亚历山德雷斯库
    • 您不应该从析构函数中抛出异常

      注:更新为参考标准中的变更:

      在C++03中
      如果异常已在传播,则应用程序将终止

      在C++11中
      如果析构函数是
      noexcept
      (默认值),则应用程序将终止

      以下内容基于C++11

      如果异常转义了
      noexcept
      函数,则如果堆栈甚至被解卷,则它是实现定义的

      以下内容基于C++03

      我的意思是立即停止。堆栈放卷停止。不再调用析构函数。都是坏东西。请参见此处的讨论

      我不同意您的逻辑,即这会导致析构函数变得更复杂。
      通过正确使用智能指针,这实际上使析构函数变得更简单,因为现在一切都是自动的。每个班级都有自己的小拼图。这里没有脑部手术或火箭科学。雷伊又赢了一场


      至于std::uncaught_exception()的可能性,我指给您看

      ,您可以通过检查来判断当前是否存在正在运行的异常(例如,我们在抛出和捕获块之间执行堆栈展开,可能是复制异常对象或类似操作)

      bool std::uncaught_exception()
      
      如果返回true,此时抛出将终止程序,否则抛出是安全的(或者至少与以前一样安全)。ISO 14882(C++标准)第15.2节和第15.5.3节对此进行了讨论


      这并不能回答在清除异常时遇到错误时该怎么办的问题,但实际上并没有什么好的答案。但是,如果您在后一种情况下等待做一些不同的事情(如记录并忽略它),而不是简单地惊慌失措,它确实可以让您区分正常退出和异常退出。

      与原始问题不同:

      现在,解除分配(最终确定, 不管你怎么称呼它)都可能失败, 在这种情况下,例外情况实际上是 让任何人上楼的唯一方法 知道我们的解除分配问题吗

      清理资源失败表示:

    • 程序员错误,在这种情况下,您应该记录失败,然后根据应用程序场景通知用户或终止应用程序。例如,释放已释放的分配

    • 分配器错误或设计缺陷。查阅文档。错误可能是用来帮助诊断程序员错误的。见上文第1项

    • 否则不可恢复的不利条件可以继续

    • 例如,C++空闲存储有一个无失败操作符删除。其他API(如Win32)提供错误代码,但只会由于程序员错误或硬件故障而失败,错误指示堆损坏或双重释放等情况

      对于不可恢复的不利条件,采用DB连接。如果由于连接断开而导致关闭连接失败--很好,您就完成了。不要扔!断开的连接(应该)会导致连接关闭,因此无需执行任何其他操作。如果有任何问题,请记录跟踪消息以帮助诊断使用问题。例如:

      class DBCon{
      public:
        DBCon() { 
          handle = fooOpenDBConnection();
        }
        ~DBCon() {
          int err = fooCloseDBConnection();
          if(err){
            if(err == E_fooConnectionDropped){
              // do nothing.  must have timed out
            } else if(fooIsCriticalError(err)){
              // critical errors aren't recoverable.  log, save 
              //  restart information, and die
              std::clog << "critical DB error: " << err << "\n";
              save_recovery_information();
              std::terminate();
            } else {
              // log, in case we need to gather this info in the future,
              //  but continue normally.
              std::clog << "non-critical DB error: " << err << "\n";
            }
          }
          // done!
        }
      };
      
      classdbcon{
      公众:
      DBCon(){
      handle=foooopendbconnection();
      }
      ~DBCon(){
      int err=foodclosedbconnection();
      如果(错误){
      if(err==E_fooConnectionDropped){
      //
      
      vector<DBHandle> to_be_closed_later;  // startup reserves space
      
      DBCon::~DBCon(){
        int err = fooCloseDBConnection();
        if(err){
          ..
          else if( fooIsRetryableError(err) ){
            try{
              to_be_closed.push_back(handle);
            } catch (const bad_alloc&){
              std::clog << "could not close connection, err " << err << "\n"
            }
          }
        }
      }
      
       void doSomething()
       {
          try
          {
             MyResource A, B, C, D, E ;
      
             // do something with A, B, C, D and E
      
             // Now we quit the scope...
             // destruction of E, then D, then C, then B and then A
          }
          catch(const MyResourceException & e)
          {
             // Do something with the exception...
          }
       }
      
          catch(const std::vector<MyResourceException> & e)
          {
             // Do something with the vector of exceptions...
             // Let's hope if was not caused by an out-of-memory problem
          }