I have a class that holds an object which destructor's can throw (it is actually a tbb::task_group, but I named it MyObject for simplicity here).
The code is like this:
#include <stdexcept>
class MyObject {
public:
MyObject() {}
~MyObject() noexcept(false) {}
};
class A {
public:
A() {}
virtual ~A() {}
};
class B : public A {
public:
B() : A() {}
~B() {}
private:
MyObject _object;
};
And the compiler raises the following error:
exception specification of overriding function is more lax than base version
I do not like the idea of spreading noexcept(false) all over the code so I was thinking about using raw pointers to MyObject, and deleting them outside of the destructor (e.g. in a Close function).
What is the best way to handle that kind of scenario?
Yes, destructors are guaranteed to be called on stack unwinding, including unwinding due to exception being thrown.
A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete . A destructor has the same name as the class, preceded by a tilde ( ~ ).
Use the obj. ~ClassName() Notation to Explicitly Call a Destructor Function. Destructors are special functions that get executed when an object goes out of scope automatically or is deleted by an explicit call by the user.
When delete is used to deallocate memory for a C++ class object, the object's destructor is called before the object's memory is deallocated (if the object has a destructor). If the operand to the delete operator is a modifiable l-value, its value is undefined after the object is deleted.
The destructor is explicitly called using the destructor function's fully qualified name. Destructors can freely call class member functions and access class member data. There are two restrictions on the use of destructors: You cannot take its address.
The destructor is not user-provided (meaning, it is either implicitly declared, or explicitly defined as defaulted on its first declaration) The destructor is not virtual (that is, the base class destructor is not virtual)
A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete. A destructor has the same name as the class, preceded by a tilde ( ~ ). For example, the destructor for class String is declared: ~String ().
In generic contexts, the destructor call syntax can be used with an object of non-class type; this is known as pseudo-destructor call: see member access operator . A class may have one or more prospective destructors, one of which is selected as the the destructor for the class.
Destructors are by default noexpect(true)
unless specified explicitly otherwise, or unless a base's or member's destructor can throw. The latter is your case. After that it is a simple mismatch between function signatures.
virtual ~A() {}
is thus actually virtual ~A() noexcept {}
which does not match virtual ~B() noexcept(false) {}
.
You have two solutions:
~B
as noexcept(true)
, but if ~MyObject
throws, the program is terminated at ~B
's boundary.~A
also noexcept(false)
,Throwing from destructors is a really bad idea. Throwing signals that the object cannot be destroyed, is that really what is happening in your code? Throw only if the correct response is to immediately terminate the program, because that is what can happen if the destructor is called as part of stack unwinding. In that case no other destructors will be called, that might cause more harm than the undead object.
If you really want to be safe and do not care about the thrown exception, you can wrap the member in a unique_ptr
with an absorbing destructor:
class B : public A {
public:
B() : A(), _object{new MyObject,deleter} {}
~B() noexcept(true) {}
private:
constexpr static auto deleter = [](MyObject* obj){ try { delete obj;}catch(...){};};
std::unique_ptr<MyObject,decltype(deleter)> _object;
};
Based on a suggestion from @463035818_is_not_a_number, it is possible to wrap the throwing class into a custom one that does not throw.
In my case, for tbb::task_group, it could be like this:
class TaskGroup {
public:
TaskGroup() {
_task = new tbb::task_group();
}
// This destructor will not throw.
// Not very pleased with "catch (...)" but not much choice due to TBB's implementation.
~TaskGroup() {
try {
delete _task;
} catch (...) {}
}
// Wrap any other needed method here.
private:
tbb::task_group* _task;
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With