9

There are some articles concluding "never throw an exception from a destructor", and "std::uncaught_exception() is not useful", for example:

But it seems that I am not getting the point. So I wrote a small testing example (see below).

Since everything is fine with the testing example I would very appreciate some comments regarding what might be wrong with it ?

testing results:

./main

    Foo::~Foo(): caught exception - but have pending exception - ignoring
    int main(int, char**): caught exception: from int Foo::bar(int)

./main 1

    Foo::~Foo(): caught exception -  but *no* exception is pending - rethrowing
    int main(int, char**): caught exception: from Foo::~Foo()

example:

// file main.cpp
// build with e.g. "make main"
// tested successfully on Ubuntu-Karmic with g++ v4.4.1
#include <iostream>

class Foo {
  public:

  int bar(int i) {
    if (0 == i)
      throw(std::string("from ") + __PRETTY_FUNCTION__);
    else
      return i+1;
  }

  ~Foo() {
    bool exc_pending=std::uncaught_exception();
    try {
      bar(0);
    } catch (const std::string &e) {
      // ensure that no new exception has been created in the meantime
      if (std::uncaught_exception()) exc_pending = true;

      if (exc_pending) {
        std::cerr << __PRETTY_FUNCTION__ 
                  << ": caught exception - but have pending exception - ignoring"
                  << std::endl;
      } else {
        std::cerr << __PRETTY_FUNCTION__
                  << ": caught exception -  but *no* exception is pending - rethrowing"
                  << std::endl;
        throw(std::string("from ") + __PRETTY_FUNCTION__);
      }
    }
  }

};

int main(int argc, char** argv) {
  try {
    Foo f;
    // will throw an exception in Foo::bar() if no arguments given. Otherwise
    // an exception from Foo::~Foo() is thrown.
    f.bar(argc-1);
  } catch (const std::string &e) {
    std::cerr << __PRETTY_FUNCTION__ << ": caught exception: " << e << std::endl;
  }
  return 0;
}

ADDED: In other words: despite of the warnings in some articles it works as expected - so what might be wrong with it?

3
  • 2
    What is your question? You seem to be observing the expected behavior of a mechanism which you shouldn't use. Are you considering using it outside a testing/experimenting context, and why? Commented Mar 19, 2010 at 5:33
  • @Potatoswatter: the question is: despite of the warnings in some articles it works as expected - so what might be wrong with it? Commented Mar 19, 2010 at 5:40
  • Fair nuff, but that's not quoting what you said, and it's impossible to know what's expected, appropriate, or safe in your context without more information. The safe route, as Herb Sutter and many others will tell you, is not to throw from a destructor in the first place. Commented Mar 19, 2010 at 6:02

3 Answers 3

9

Herb Sutter is referring to a different issue. He's talking about:

try
{
}
catch (...)
{
    try
    {
        // here, std::uncaught_exception() will return true
        // but it is still safe to throw an exception because
        // we have opened a new try block
    }
    catch (...)
    {
    }
}

So the problem is that if std::uncaught_exception() returns true you don't know for sure whether you can safely throw an exception or not. You end up having to avoid throwing an exception when std::uncaught_exception() returns true just to be safe.

Sign up to request clarification or add additional context in comments.

2 Comments

@Samuel: ... referring to a different isssue ... That's what I also thought. And - since I don't throw an exception when uncaught_exception() returns true - do you believe that my approach is a safe one?
That's actually not right. Your exception is caught at that point. You should've put the try/catch in a destructor, that's invoked while stack unwinding (due to an active exception). In that situation, an exception is actively unwinding the stack, but you're right then inside a try/catch block where you can throw an exception.
6

There's nothing technically wrong with your code. It's perfectly safe in that you will never accidentally terminate because you threw an exception when it was not safe to. The issue is that it also is not useful, in that it will occasionally also not throw an exception when it is safe to. Your destructor's documentation basically has to say "this might or might not throw an exception."

If it occasionally won't throw an exception, you might as well never throw an exception. That way, you're at least consistent.

Comments

1

Herb Sutter is talking about the situation when an object of class T is destroyed while there is an uncaught exception in an object of class U. std::uncaught_exception() would return true in the T destructor. The destructor would be unable to find out whether it's called during stack unwinding. If it is, it must not throw, otherwise it's business as usual.

The class U would have a problem using class T in the destructor. U would find itself dealing with a useless T object that would refuse to do anything risky in its destructor (that could include writing a log file or committing a transaction to a database).

Herb Sutter suggests never throwing in a destructor, which is a good idea. However, the C++17 offers another option. It introduced std::uncaught_exceptions(), which can be used to find out whether the destructor can throw. Following example shows the problem if complied in C++14 mode. If compiled in C++17 mode, it would work correctly.


#include <exception>
#include <iostream>
#include <string>

class T
{
  public:

    ~T() noexcept(false)
    {
#if __cplusplus >= 201703L
      // C++17 - correct check
      if (std::uncaught_exceptions() == uncaught_exceptions_)
#else
      // Older C++ - incorrect check
      if (!std::uncaught_exception())
#endif
      {
        throw (std::string{__PRETTY_FUNCTION__} + " doing real work");
      }
      else
      {
        std::cerr << __PRETTY_FUNCTION__ << " cowardly quitting\n";
      }
    }

  private:

#if __cplusplus >= 201703L
    const int uncaught_exceptions_ {std::uncaught_exceptions()};
#endif
};

class U
{
  public:

    ~U()
    {
      try
      {
        T t;
      }
      catch (const std::string &e)
      {
        std::cerr << __PRETTY_FUNCTION__ << " caught: " << e << '\n';
      }
    }
};

int main()
{
  try
  {
    U u;
    throw (std::string{__PRETTY_FUNCTION__} + " threw an exception");
  }
  catch (const std::string &e)
  {
    std::cerr << __PRETTY_FUNCTION__ << " caught: " << e << '\n';
  }
  return 0;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.