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.