Proper use of std :: uncaught_exception in destructor

There are several articles concluding "never throw an exception from the destructor", and "std :: uncaught_exception () is not useful", for example:

But it seems I do not understand. Therefore, I wrote a small testing example (see below).

Since everything is okay with the test example, I would really appreciate some comments regarding what could be wrong with it?

test results:

./the main

     Foo :: ~ Foo (): caught exception - but have pending exception - ignoring
     int main (int, char **): caught exception: from int Foo :: bar (int)

./main 1

     Foo :: ~ Foo (): caught exception - but * no * exception is pending - rethrowing
     int main (int, char **): caught exception: from Foo :: ~ Foo ()

Example:

// file main.cpp // build with eg "make main" // tested successfully on Ubuntu-Karmic with g++ v4.4.1 #include <iostream> class Foo { public: int bar(int i) { if (0 == i) throw(std::string("from ") + __PRETTY_FUNCTION__); else return i+1; } ~Foo() { bool exc_pending=std::uncaught_exception(); try { bar(0); } catch (const std::string &e) { // ensure that no new exception has been created in the meantime if (std::uncaught_exception()) exc_pending = true; if (exc_pending) { std::cerr << __PRETTY_FUNCTION__ << ": caught exception - but have pending exception - ignoring" << std::endl; } else { std::cerr << __PRETTY_FUNCTION__ << ": caught exception - but *no* exception is pending - rethrowing" << std::endl; throw(std::string("from ") + __PRETTY_FUNCTION__); } } } }; int main(int argc, char** argv) { try { Foo f; // will throw an exception in Foo::bar() if no arguments given. Otherwise // an exception from Foo::~Foo() is thrown. f.bar(argc-1); } catch (const std::string &e) { std::cerr << __PRETTY_FUNCTION__ << ": caught exception: " << e << std::endl; } return 0; } 

ADDED . In other words: despite the warnings in some articles, it works as expected — so what could be wrong with it?

+7
c ++ exception
source share
3 answers

There is nothing technically wrong in the code. It is absolutely safe that you will never end by accident, because you threw an exception when it was unsafe. The problem is that it will also not come in handy, because sometimes it will also not throw an exception if it is safe. Your destructor documentation should basically say, "this may or may not throw an exception."

If this does not throw an exception from time to time, you can also never throw an exception. So you are at least consistent.

+5
source share

Herb Sutter refers to another issue. He is talking about:

 try { } catch (...) { try { // here, std::uncaught_exception() will return true // but it is still safe to throw an exception because // we have opened a new try block } catch (...) { } } 

So the problem is that if std::uncaught_exception() returns true, you do not know for sure whether you can safely throw an exception or not. You should avoid throwing an exception when std::uncaught_exception() returns true just to be safe.

+7
source share

Herb Sutter talks about a situation where an object of class T destroyed, while in an object of class U there is an uncaught exception. std::uncaught_exception() will return true in the T destructor. The destructor will not be able to find out if he called it during the unwinding of the stack. If so, he should not quit, otherwise this is the case as usual.

Class U will have a problem using class T in the destructor. U discovers that it is dealing with a useless object T that refuses to do anything risky in its destructor (this may include writing a log file or committing a transaction to a database).

Grass Sutter suggests never throwing a destructor, which is a good idea. However, C ++ 17 offers another option. It introduces std::uncaught_exceptions() , which can be used to find out if the destructor can throw. The following example shows the problem if it is running in C ++ 14 mode. If it is compiled in C ++ 17 mode, it will work correctly.

 #include <exception> #include <iostream> #include <string> class T { public: ~T() noexcept(false) { #if __cplusplus >= 201703L // C++17 - correct check if (std::uncaught_exceptions() == uncaught_exceptions_) #else // Older C++ - incorrect check if (!std::uncaught_exception()) #endif { throw (std::string{__PRETTY_FUNCTION__} + " doing real work"); } else { std::cerr << __PRETTY_FUNCTION__ << " cowardly quitting\n"; } } private: #if __cplusplus >= 201703L const int uncaught_exceptions_ {std::uncaught_exceptions()}; #endif }; class U { public: ~U() { try { T t; } catch (const std::string &e) { std::cerr << __PRETTY_FUNCTION__ << " caught: " << e << '\n'; } } }; int main() { try { U u; throw (std::string{__PRETTY_FUNCTION__} + " threw an exception"); } catch (const std::string &e) { std::cerr << __PRETTY_FUNCTION__ << " caught: " << e << '\n'; } return 0; } 
0
source share

All Articles