Exceptions in C ++ destructors if there is no uncaught_exception

People quite strongly objected to throwing exceptions from destructors. Take this answer as an example. I wonder if std::uncaught_exception() be used to portablely determine if we are in the process of stack promotion due to some other exception.

I find myself deliberately throwing exceptions in destructors. To mention two possible use cases:

  • Some kind of resource flushing that includes flush buffers, so a crash probably means truncated output.
  • Destroying an object with std::exception_ptr , which may contain an exception that occurs in another thread.

Just ignoring these exceptional situations seems unfair. And there is a chance that by throwing an exception, the exception handler may provide more useful contextual information than if the destructor himself wrote to std::cerr . Also, throwing exceptions for all failed statements is an important part of my unit testing. In this case, the error message followed by the ignored error condition will not work.

So my question is: is it okay to throw exceptions, unless another exception is handled , or is there a reason not to do this?

To put this in code:

 Foo::~Foo() { bool success = trySomeCleanupOperation(); if (!success) { if (std::uncaught_exception()) std::cerr << "Error in destructor: " << errorCode << std::endl; else throw FooOperationFailed("Error in destructor", errorCode); } } 

As far as I can tell, this should be safe and in many cases is better than excluding the exception altogether. But I would like to check it out.

+8
c ++ exception destructor
source share
2 answers

Herb Sutter wrote on this topic: http://www.gotw.ca/gotw/047.htm

Its conclusion is to never throw from the destructor, always report an error using a mechanism that you will use in the case when you cannot throw.

Two reasons:

  • It doesn’t always work. Sometimes uncaught_exception returns true, and yet it is safe to throw.
  • it is a poor design to have the same error reported in two different ways, both of which the user will have to consider if they want to know about the error.

Note that for any given reusable code snippet, there is no way to know for sure that it will never be called during the unwinding of the stack. No matter what your code does, you cannot be sure that any user will want to invoke it from the destructor using try/catch to handle its exceptions. Therefore, you cannot rely on uncaught_exception , which always returns true if it is safe to throw, except, perhaps by documenting a function, "cannot be called from destructors." If you resorted to this, then all subscribers would also have to document their functions, "should not be called from destructors," and you have even more annoying restrictions.

Among other things, the nothrow guarantee is valuable to users - it helps them write code that excludes code if they know that the particular thing they are doing will not drop.

One way is to give your class a close member function that calls trySomeCleanupOperation and throws if it fails. The destructor then calls trySomeCleanupOperation and writes or suppresses the error, but does not throw. Then users can call close if they want to know if their operation was successful or not, and just let the destructor handle it if they are not interested (including the case when the destructor is called as part of the stack expansion, because an exception was thrown before moving to the user call close ). β€œAha!” You say, β€œbut that defeats the RAII goal, because the user must remember to call close !”. Yes, a little, but the question is whether RAII is doing everything you want. It's impossible. The question is whether it will consistently do less than you would like (you want it to throw an exception if trySomeCleanupOperator does not work), or less unexpectedly when used while unwinding packets.

Also, throwing exceptions for all failed statements is an important part of my unit testing.

Probably a mistake - your unit testing platform should be able to handle terminate() as a test failure. Suppose the statement fails when the stack fails - of course you want to write this, but you cannot do this by throwing an exception, so you draw yourself in the corner. If your statements cease, you may find them as completion.

Unfortunately, if you are done, you will not be able to run the rest of the tests (at least not in this process). But if the statement fails, then generally speaking, your program is in an unknown and potentially dangerous state. Therefore, once you lose the statement, you still cannot rely on doing anything else in the process. You might consider developing your own test environment to use several processes, or simply accepting that a sufficiently serious test failure would hamper the rest of the tests. Outwardly to the test structure, you might think that your test run has three possible results: "everything went well, something failed, the tests crashed." If the test run is not completed, you do not consider it as a pass.

+10
source share

This standard talks about dtors and exceptions:

15.2

(...)

The process of calling destructors for automatic objects built along the path from the try block to the point where the exception is thrown is called "stack expansion". If the destructor called during the unwinding of the stack exits with an exception, std :: terminate (15.5.1) is called. [ Note: therefore, destructors usually need exceptions and prevent them from propagating from the destructor. -end note ]

Since you asked a very ambiguous question, the answer depends:

  • Can you make an exception in dtor so the application does not crash? Yes , you can (in terms: it will compile, and sometimes it will work correctly).
  • Should an exception be excluded from dtor? No , you should not, because it can (and usually will) cause problems.

I would say that the need to throw an exception from dtor is a sign of poor design. It seems that you are doing something in your destructor, this should be done elsewhere.

+3
source share

All Articles