Detecting active exceptions in the destructor
Asked Answered
L

4

8

I have a class that is using RAII for cleanup in case something goes wrong. This means the class contains a flag, that tells it whether the work has been completed, and if this flag is not set when the constructor is called it is performing it's cleanup tasks and produces a log messages. Now I would like this class to become one step more clever, i.e. it should find out, if the error happened, because the work was abborted (i.e. an exception was thrown and the destructor got called) or because someone was missusing this class and never actually finished the work. This means I would have to find out in the destructor, if an exception is active. If one is found I would produce a log message, possibly printing the content of the exception and then rethrowing it. I am guessing something like this.

Foo::~Foo () {
  try { /* do not know what to put here */ }
  catch( const std::exception & ex ) {
     // produce error in log
     throw;
  }
  catch( ... ) {
    // produce some other log message
    throw;
   }
}

However I am not sure if this would work at all, since the exception is active already before the destructor is called and does not originate from the try block. Also I am using a throw; inside the destructor and throwing an exception at this point is a really bad Idea. So I would not do it, unless the standard guarantees clearly that this case is an exception (no pun intended) to this rule (which I don't know).

So is this possible at all, or should I handle this situation in some other way?

Leper answered 1/4, 2011 at 9:34 Comment(0)
W
5

There was no way to use bool std::uncaught_exception() correctly, so it was removed in C++20. We now have int std::uncaught_exceptions().

You need to call it twice: once beforehand (e.g. in the constructor), then again in the destructor. If the second call returned a larger number, it means the destructor was called due to an exception. Otherwise it will return the same number.

struct A
{
    int e = std::uncaught_exceptions();

    ~A()
    {
        if (std::uncaught_exceptions() > e)
            // An exception was thrown.
    }
};
Wiencke answered 25/4, 2022 at 19:19 Comment(0)
E
12

You can use std::uncaught_exception() which returns true if an exception has been thrown but a catch has not yet handled it. You can use this check in your destructor to make decisions about what it should or should not do.

A note of caution, good programming guidelines usually dictate that having your destructor behave significantly differently under different circumstances is usually not a good idea. So use this function, but don't abuse it. A common use is to have a destructor that only throws if there is no active uncaught exception (this function returns false). But that type of behavior is generally not good design. If the condition was bad enough to warrant an exception, it probably shouldn't be ignored. And destructors should not throw exceptions anyways.

Example:

Foo::~Foo()
{
    if (std::uncaught_exception()) 
    {
        std::cerr << "Warning: Can't cleanup properly" << std::endl;
        return;
    }
    else
    {
        // ...
    }
}
Elective answered 1/4, 2011 at 10:1 Comment(3)
this example is unsafe, operator<< can throw ios_base::failure, you need an extra try/catch(...) around the logFrediafredie
That's simply broken. What if there was already an exception on the way when Foo is constructed?Belletrist
@Belletrist This problem seems to have been acknowledged by the standard committee. See the answer below, which indicates that std::uncaught_exception() was deprecated and replaced with an improved version.Leper
W
5

There was no way to use bool std::uncaught_exception() correctly, so it was removed in C++20. We now have int std::uncaught_exceptions().

You need to call it twice: once beforehand (e.g. in the constructor), then again in the destructor. If the second call returned a larger number, it means the destructor was called due to an exception. Otherwise it will return the same number.

struct A
{
    int e = std::uncaught_exceptions();

    ~A()
    {
        if (std::uncaught_exceptions() > e)
            // An exception was thrown.
    }
};
Wiencke answered 25/4, 2022 at 19:19 Comment(0)
C
2

It is not possible to do in a safe manner.

What you can do is to check your parameters in the destructor, and that should tell if the processing was completed.

By the way, why are you not logging the error in the catch block where you can actually handle the error? There you should know that the processing was terminated with the error.

Canfield answered 1/4, 2011 at 9:48 Comment(1)
Yes, I am already checking the parameters. However the stuff my class is doing is of the kind where you have multiple operations to perform, and then you do an atomic commit. However I want to check, if something went really wrong, the other code never gets to the commit step. On the other hand the logs now will not indicate, whether something bad happened, and the commit was not executed, or if the programmer just forgot to write the commit. This second case is the one I want to catch.Leper
N
1

Have a look at this question of mine and the responses.

Basically, you can rethrow the currently active exception using a simple throw in the first try block. However, this is only safe if you know for sure that there is an exception currently being handled, i.e the destructor is called from within a catch block. Otherwise, the throw will invoke std::terminate().

Foo::~Foo () {
  try {  throw; } // only if you *know* that an exception is active - bad thing for a generic destructor
  catch( const std::exception & ex ) {
     // produce error in log

  }
  catch( ... ) {
    // produce some other log message

   }
}

However, throwing exceptions from within destructors is frowned upon, so I wouldn't rethrow the exceptions afterwards. Atfer all, the whole thing doesn't look like a good idea to me.

The exception handling pattern is fine, but I wouldn't do it in an unrelated destructor. Again, see the referenced question.

Neuburger answered 1/4, 2011 at 9:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.