析构函数的C ++抛出异常

You're implementing a beautiful class. It's just wonderful - the most perfect code you've ever written, except... for some reason it keeps crashing and you don't know why. You've debugged the code and discovered it happens when your class goes out of scope and needs to throw an exception from the destructor. Tested in isolation, this works fine but integrated into the main code base it causes a crash. You know there are exception handlers that should deal with this and yet it still crashes. This doesn't make sense. Why is the crashing?

您正在实施一个漂亮的课程。 真是太好了-您所编写的最完美的代码,除了...由于某种原因,它一直崩溃,您也不知道为什么。 您已经调试了代码,发现当类超出范围并需要从析构函数中引发异常时,它就会发生。 经过隔离测试,这可以正常工作,但集成到主要代码库中会导致崩溃。 您知道有些异常处理程序应对此进行处理,但仍然会崩溃。 这没有道理。 为什么会崩溃?

As part of the development of this class it was necessary to write some complex "clean-up" code in the class destructor, but there is the possibility that something could fail during the clean up. For example, maybe our use case for the class is an object that represents a database connection and the destructor is finalising any outstanding transactional data commits before the object goes out of scope.

作为此类开发的一部分,有必要在类析构函数中编写一些复杂的“清除”代码,但是有可能在清除过程中某些操作可能会失败。 例如,也许我们的类用例是一个代表数据库连接的对象,并且析构函数在该对象超出范围之前完成所有未完成的事务数据提交。

Your class can't just leave these commits unfinalised as that could leave the database in an inconsistent state, but the process can fail. What do you do? You have to handle these commits but they can go wrong and so you have no choice but to throw an exception of course, right? Wrong! This really is about the most dangerous thing you could possibly do! Also, if your class is written correctly it should be completely unnecessary.

您的类不能仅仅使这些提交未完成,否则可能会使数据库处于不一致状态,但是该过程可能会失败。 你是做什么? 您必须处理这些提交,但是它们可能出错,因此您别无选择,只能抛出异常,对吗? 错误! 这确实是您可能要做的最危险的事情! 同样,如果您的班级写得正确,则完全没有必要。

Here's a question for you. Let's say the destructor of your class is being executed because the stack is unwinding due to another exception that was already thrown. The destructor of your class encounters its own error and so throws its own exception. Question: what happens next?

这是您的问题。 假设您正在执行类的析构函数,因为由于另一个已抛出的异常,堆栈正在展开。 您的类的析构函数会遇到自己的错误,因此会抛出自己的异常。 问题:接下来会发生什么?

If your answer was that the new exception gets thrown in place of the original please go to the back of the class, stand in the corner and face the wall. You're wrong. Very wrong. Badly, dangerously, seriously wrong! The C++ Standard document is very clear on what happens next: your program is terminated, abruptly! Yes, you read that correctly. This is not a mis-print or even me messing with you. Your program is just terminated. Good bye dear. 

如果您的答案是新的异常被替换为原来的异常,请转到课程的后面,站在角落并面对墙。 你错了。 错了 严重,危险,严重错误! C ++ Standard文档非常清楚下一步将发生什么:您的程序突然终止! 是的,你没看错。 这不是打印错误,甚至不是我惹你。 您的程序刚刚终止。 再见,亲爱的。

Don't believe me? Try it!

不相信我吗 试试吧!

#include 
#include 

class StupidClass
{
   public:
      ~StupidClass()
      {
         // this is just asking for trouble!
         throw std::runtime_error("this is stupid");
      }
};

int main()
{
   try
   {
      auto && sc = StupidClass();
      throw std::runtime_error("something wicked this way comes");
   }
   catch(...) // catch all just used for demonstration purposes!
   {
      // I can't help you - code never gets here!
      std::cerr << "handled error!" << std::endl;
   }
}

Now, to be clear (in case I haven't been so far), termination is abrupt. It is not a nice and friendly request to exit, it is an abrupt and immediate termination. No further stack unwinding takes place, no more destructors are called and there is no opportunity to purge data and close files. Your program is given no chance to perform any further clean up.

现在,要弄清楚(如果我还没有来的话),终止是突然的。 这不是一个友好的退出请求,而是一个突然的立即终止。 没有进一步的堆栈展开操作,没有调用更多的析构函数,也没有机会清除数据和关闭文件。 您的程序没有机会进行任何进一步的清理。

In the C++-3 standard, this only happens if your destructor emits an exception during a current stack-unwinding but in the new C++11 standard it's even more specific: even if the stack isn't currently unwinding, due to another exception, the result is still immediate termination of your program. This is now the default behaviour on all class destructor in C++11, they terminate if an exception is allowed to emanate from the destructor.

在C ++-3标准中,仅当您的析构函数在当前堆栈展开过程中发出异常时才会发生这种情况,而在新的C ++ 11标准中,它更具体:即使堆栈当前未展开,由于另一个异常,结果仍然是程序立即终止。 现在,这是C ++ 11中所有类析构函数的默认行为,如果允许从析构函数中发出异常,则它们将终止。

So, the very blunt point of this article: DO NOT let exceptions emit from the destructors of C++ classes. Ever!!!

因此,本文最直截了当的要点是:

Okay, so what do we do if our destructor needs to handle a failure? Easy, you don't put such code into the destructor.

好的,如果我们的析构函数需要处理故障,我们该怎么办? 容易,您无需将此类代码放入析构函数中。

Instead, you should put such clean-up code in a separate function that can be called before the class goes out of scope. In the case of our example database connection you might add a "flush" method that the user is expected to call before the class goes out of scope.

相反,您应该将此类清理代码放在单独的函数中,可以在类超出范围之前调用该函数。 在我们的示例数据库连接示例中,您可以添加一个“ flush”方法,要求用户在类超出范围之前调用它。

Ensuring you've added this function provides the user the option of finalising usage of the class before the destructor is called. This avoids the situation of the destructor needing to do the work that may fail. Given our example use case, you've now given the user the ability to ensure the object is flushed before the class goes out of scope. This affords them the opportunity to deal with any errors that may happen during the clean-up process; the user can catch and deal with them.

确保已添加此函数,使用户可以选择在调用析构函数之前完成对类的使用。 这避免了析构函数需要执行可能会失败的工作的情况。 给定我们的示例用例,您现在已经向用户提供了确保在类超出范围之前刷新对象的能力。 这使他们有机会处理清理过程中可能发生的任何错误; 用户可以抓住并处理它们。

You expect the user to have already called this clean-up function by the time the class is destructed; however, for the sake of ensuring your class is a good citizen (who always clean up after themselves) the destructor should still call the clean-up method if it wasn't called by the user, but it MUST swallow any exceptions that are emitted if the clean-up fails. Unfortunately, this means the user of your class has no idea of the failure, but that's their fault for not following the correct usage instructions for your class!

您希望用户在类被销毁之前已经调用了此清理函数。 但是,为了确保您的班级是一个好公民(总是自己清理自己),如果用户未调用析构函数,则析构函数仍应调用该清理方法,但该方法必须吞下所有发出的异常如果清理失败。 不幸的是,这意味着您的班级用户不知道失败的原因,但这是他们不遵守班级正确使用说明的错误!

My advice would be to add a debug assert to your class such that in a debug build; if the clean-up is not performed by the time the destructor is called it should fire an assert. At least in this way the developer will know s/he has used your class wrongly.

我的建议是在类中添加调试断言 ,以便在调试版本中进行。 如果在调用析构函数之前尚未执行清理,则应触发一个断言。 至少以这种方式,开发人员将知道他/他错误地使用了您的课程。

Time for an example:

时间为例:

#include <cassert>
#include <stdexcept>
#include <iostream>

class StupidClass
{
   public:

      void CleanUp()
      {
         // I'm a stupid function that always throws - duh!
         dirty_ = false;
         throw std::runtime_error("this is stupid");
      }

      ~StupidClass()
      {
         // if this triggers class is being used wrong!
         assert("your class is still dirty" && !dirty_);

         try
         {
            // just in case the user of this class was dumb and didn't read
            // the instructions on how to safely and correctly destroy me.
            if(dirty_)
            {
               // if it fails all we can do is ignore (maybe log to log file)
               CleanUp();
            }
         }
         catch(...)
         {
            // Eeek, dragons! Sadly, we've had to ignore them.
            assert("clean-up failed" && false);
         }
      }

   public:
      bool dirty_; // if set the class needs cleaning
};

int main()
{
   try
   {
      auto && sc = StupidClass();
      sc.CleanUp(); // errors but at least destructor won't terminate now
   }
   catch(...) // catch all just used for demonstration purposes!
   {
      std::cerr << "handled error!" << std::endl;
   }
}

As you can see, there is a specific function provided to allow clean-up and the user of the class is expected to call it before the class is destructed. As a fail-safe the destructor will call the clean-up method if the class is still dirty; however, this is a last resort and if there are any errors they'll slip by silently (unless logged). In a debug build an "assert" will trigger if the class is still dirty upon destruct and also if the clean-up emits an error. In a release build this will be silently ignored.

如您所见,提供了一个允许清除的特定函数,并且该类的用户应在销毁该类之前对其进行调用。 作为故障保险,如果类仍然很脏,则析构函数将调用清除方法。 但是,这是万不得已的方法,如果有任何错误,它们会悄悄地溜走(除非记录下来)。 在调试版本中,如果该类在销毁时仍然很脏,并且清理产生错误,则将触发“断言”。 在发行版本中,将默默地忽略它。

翻译自: https://www.experts-exchange.com/articles/18350/C-Throwing-exceptions-from-destructors.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值