7

I'm trying to write a unit test that detects an invalid use of the lock() feature of my class. In order to do so, I want to use the destructor and throw an exception from there. Unfortunately, instead of catching the exception, g++ decides to call std::terminate().

There is a very simplified version of the class:

class A
{
public:
    A() : f_lock(0) {}
    ~A() { if(f_lock) throw my_exception("still locked"); }
    lock() { ++f_lock; }
    unlock() { --f_lock; }
private:
    int f_lock;
};

There is a valid test:

A *a = new A;
a->lock();
...
a->unlock();
delete a;

There is the invalid test I'm trying to write:

A *a = new A;
a->lock();
...
bool success = false;
try
{
    delete a;
}
catch(my_exception const&)
{
    success = true;
}
catch(...)
{
    // anything else is a failure
}
if(!success)
{
    // test failed...
    CPPUNIT_ASSERT(!"test failed");
}

Right now, the delete calls std::terminate() even though the throw is not being called when another exception is active. (i.e. std::uncaught_exception() is false.) And also I clearly am catch all exceptions!

Am I doing something wrong, or is g++ programmed to do that always in destructors?


Update:

The answer by dyp in the comments below works! The following does not directly call std::terminate():

    ~A() noexcept(false) { throw ...; }

Also for reference about why you do not want a throw in a destructor, this page is excellent;

https://www.securecoding.cert.org/confluence/display/cplusplus/ERR33-CPP.+Destructors+must+not+throw+exceptions


For clarification, there is the full version of the destructor. As we can see I first post a message (it generally goes in your console, may go to a log too). Second I make sure we're not already managing an exception. Finally, I throw an exception named exception_exit which is expected to force a terminate() although in a GUI application you may want to show a MessageBox of some sort to let the user know something happened (since you can capture the Message, you can display that to the user) and then force an application shut down.

Node::~Node() noexcept(false)
{
    if(f_lock > 0)
    {
        // Argh! A throw in a destructor... Yet this is a fatal
        // error and it should never ever happen except in our
        // unit tests to verify that it does catch such a bug
        Message msg(message_level_t::MESSAGE_LEVEL_FATAL, err_code_t::AS_ERR_NOT_ALLOWED);
        msg << "a node got deleted while still locked.";

        // for security reasons, we do not try to throw another
        // exception if the system is already trying to process
        // an existing exception
        if(std::uncaught_exception())
        {
            // still we cannot continue...
            std::abort();
        }

        throw exception_exit(1, "a node got deleted while still locked.");
    }
}

Also, another detail, you are expected to use the NodeLock object to manage the f_lock flag. That is exception safe since it uses RAII (i.e. a scoped lock). However, at this point I did not want to force the user to make use of the NodeLock to lock/unlock a node, hence this test in the destructor.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
  • 2
    Since C++11, most destructors are `noexcept` by default. Try declaring it as `noexcept(false)` (or better yet, try to design your classes not to throw in a destructor). – dyp Oct 13 '14 at 00:23
  • 1
    I'm quite curious what you're expecting to happen. Say you catch the exception -- you have an object that is out of scope but not destructed. What do you do? – David Schwartz Oct 13 '14 at 00:31
  • @dyp, thank you for the answer! That worked. I'm sorry I cannot give you points for it since Matt & Jonathan decided that it was a duplicate of an argument of why you should not do what I'm trying to do, but NO ANSWER to my question. Very annoying if you ask me. – Alexis Wilke Oct 13 '14 at 00:42
  • @DavidSchwartz, The answer is in my very first sentence: I'm writing a Unit Test. Safe code is safe only if the tests run the exact same code and the final code. If you start adding things like `#ifdef DEBUG`, then you have two versions of the code and that's definitively not as safe. – Alexis Wilke Oct 13 '14 at 00:44
  • @AlexisWilke You didn't really answer my question. What are you expecting to happen? When the exception is caught, the object is out of scope but not destructed, right? What are you expecting to happen in that case? – David Schwartz Oct 13 '14 at 00:49
  • @AlexisWilke you're right, this should not be a duplicate. The change to make the destructor default to `noexcept(true)` was made in C++11 so it deserves its own question. The duplicate link does explain why it is a bad idea to throw from a destructor though. – M.M Oct 13 '14 at 00:51
  • @dyp the question is open right now if you want to leave an answer. I'd certainly upvote it since I didn't know this. – Mark Ransom Oct 13 '14 at 02:41

2 Answers2

5

In C++11 the noexcept keyword was added. This can be used in function exception specifications:

  • noexcept(true) is the same as throw(), i.e. this function terminates if anything is thrown
  • noexcept(false) means the function may throw anything

For most functions, they don't have an exception-specification unless you give them one. A function with no exception-specification may throw anything.

There is a special case for destructors though, found in C++11 [class.dtor]/3:

A declaration of a destructor that does not have an exception-specification is implicitly considered to have the same exception-specification as an implicit declaration (15.4).

The referenced rule, 15.4, says that implicitly-declared special member functions always have an exception-specification. The specification is determined by the following rule, [except.spec]/14:

An implicitly declared special member function (Clause 12) shall have an exception-specification. If f is an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f’s implicit definition; f shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions.

The "function it directly invokes" in this clause means the destructor of any member variable or base class (applied recursively). If there is no such function , then there are no exceptions allowed by such a function, so the default is noexcept(true).

We could summarise the portion of the above quote relating to your code like this:

  • If all subobjects either have no destructor, or implicitly-generated destructor, or destructor declared as noexcept(true) or equivalent; then this class's destructor defaults to noexcept(true).

So, changing your destructor to have noexcept(false) would reproduce the behaviour of C++03.


In C++03 none of this appeared and your destructor would have defaulted to allowing all exceptions. I don't know for sure why this change was made in C++11, but it may be because it is a bad idea to throw from a destructor unless you really know what you are doing.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Related: http://stackoverflow.com/questions/16540725/c11-exceptions-destructor-allows-to-throw-now – M.M Oct 13 '14 at 02:56
  • I guess it is a better default to not allow a throw by default, except that the compiler should tell you so at compile time and not just call std::terminate instead of throwing. Maybe there is a warning for that case... Otherwise, the reason for which I like to have an exception is to be able to test the potential bug in my unit test. If the system just terminates, I cannot write a simple unit test (writing a binary to terminates abnormally is not proof that the test succeeded.) In my code the exception is called `exception_exit` and when you catch that exception you are expected to exit. – Alexis Wilke Oct 13 '14 at 03:54
1

Detailed explanation available at http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html. Better explanation than my own words :)

Excerpt :

You can throw an exception in a destructor, but that exception must not leave the destructor; if a destructor exits by emitting an exception, all kinds of bad things are likely to happen because the basic rules of the standard library and the language itself will be violated. Don’t do it

Reno
  • 33,594
  • 11
  • 89
  • 102
xiaodong
  • 952
  • 1
  • 7
  • 19