2
votes

I've learned that if throwing out of destructor program will abort if that happens during stack unwinding, because then more than 1 exception will propagate.

Here is example with comment that demonstrates this:

class Foo
{
public:
    ~Foo()
    {
        ReleaseResources();
    }

private:
    int* pInt;

    void ReleaseResources()
    {
        if (!pInt)
            throw 0;
        else delete pInt;
    }
};

int main() try
{
    {
        Foo local;
        throw 1;
    } // aborting here, because now 2 exceptions are propagating!

    return 0;
}
catch (int& ex)
{
    return ex;
}

However I have a class hierarchy where one of destructors calls a function that may throw, and because of that entry hierarchy is poisoned meaning that now all destructors are marked as noexcept(false).

while this is OK for compiler to insert exception code, it is not OK for the user of these classes because it does not prevent aborting the program if scenario from above code sample happens.

Because I want destructors to be exception safe, I come to idea to mark them all as noexcept but handle possible exceptions inside destructor like this:

Same sample but reworked such that abort not possible, and destructors exception safe:

class Foo
{
public:
    ~Foo() noexcept
    {
        try
        {
            ReleaseResources();
        }
        catch (int&)
        {
            // handle exception here
            return;
        }
    }

private:
    int* pInt;

    void ReleaseResources()
    {
        if (!pInt)
            throw 0;
        else delete pInt;
    }
};

int main() try
{
    {
        Foo local;
        throw 1;
    } // OK, not aborting here...

    return 0;
}
catch (int& ex)
{
    return ex;
}

The question is, is this normal approach to handle exceptions inside destrucotrs? are there any examples that could make this design go wrong?

Primary goal is to have exception safe destructors.

Also a side question, in this second example, during stack unwinding there are still 2 exceptions propagating, how is that no abort is called? if only one exception is allowed during stack unwinding?

2

2 Answers

1
votes
~Foo() noexcept

The noexcept is redundant in this case, because there are no sub objects with potentially throwing destructor. The destructor would be implicitly noexcept without the

The question is, is this normal approach to handle exceptions inside destrucotrs?

Try-catch is in general the way exceptions are handled whether inside of destructors or otherwise.

However, a better solution in this particular case would be:

void ReleaseResources()
{
    delete pInt;
}

There's no need to throw here, and it will be simpler to not do so.

Also a side question, in this second example, during stack unwinding there are still 2 exceptions propagating, how is that no abort is called?

Because it is allowed.

1
votes

The question is, is this normal approach to handle exceptions inside destrucotrs? are there any examples that could make this design go wrong?

Yes, you can avoid throwing destructors like this if your // handle exception here code actually handles the exception. But in practice, if you are throwing an exception during destruction it usually implies that there is no good way to handle the exception.

Throwing from a destructor means that some sort of cleanup failed. Maybe a resource is leaked, data couldn't be saved and is now lost or some internal state couldn't be set or reverted. Whatever the cause if you could avoid or fix the problem you wouldn't have to throw in the first place.

Your solution to this bad situation (throwing destructor) only works when you aren't actually in the bad situation. In practice, if you try to apply this you will find that there isn't anything to write // handle exception here, except maybe warning the user or logging the problem.


if only one exception is allowed during stack unwinding?

There is no such rule. The problem with throwing during stack unwinding is if an uncaught exception escapes from a destructor. If the destructor throws and catches exceptions internally, it has no effect on ongoing stack unwindings. std::terminate explicitly states when stack unwinding ends in termination (link) :

In some situations exception handling must be abandoned for less subtle error handling techniques. These situations are:

[...]

-- when the destruction of an object during stack unwinding terminates by throwing an exception, or

[...]