Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Throwing data member's destructor

Tags:

c++

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?

like image 636
Uraza Avatar asked Sep 27 '21 08:09

Uraza


People also ask

Does throw Call destructor?

Yes, destructors are guaranteed to be called on stack unwinding, including unwinding due to exception being thrown.

Is destructor a member function?

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 ( ~ ).

How do you explicitly call a destructor?

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.

Is delete () a destructor?

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.

How do destructors work in Java?

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.

Is the destructor of a class user provided?

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)

What is destructor in C++?

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 ().

How do you call a destructor from a non class?

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.


Video Answer


2 Answers

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:

  1. Explicitly mark ~B as noexcept(true), but if ~MyObject throws, the program is terminated at ~B's boundary.
  2. Mark ~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;
};
like image 150
Quimby Avatar answered Oct 24 '22 09:10

Quimby


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;
};
like image 43
Uraza Avatar answered Oct 24 '22 09:10

Uraza