在析构函数中正确使用std :: uncaught_exception

时间:2010-03-19 05:24:12

标签: c++ exception

有些文章总结“永远不会从析构函数中抛出异常”,而“std :: uncaught_exception()没有用”,例如:

但似乎我没有明白这一点。所以我写了一个小测试示例(见下文)。

由于测试示例一切正常,我会非常感谢有关它可能出错的一些评论吗?

测试结果:

./主

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

示例:

// 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 :换句话说:尽管有些文章中的警告可以按预期运行 - 那么它可能出现什么问题?

3 个答案:

答案 0 :(得分:8)

Herb Sutter指的是一个不同的问题。他在谈论:

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

所以问题是如果std::uncaught_exception()返回true,你不确定是否可以安全地抛出异常。 std::uncaught_exception()返回true时最终必须避免抛出异常。

答案 1 :(得分:6)

您的代码在技术上没有任何问题。这是非常安全的,因为你不会意外地终止,因为你在不安全的情况下抛出异常。问题是它也没有用,因为它有时也不会在安全的情况下抛出异常。您的析构函数的文档基本上必须说“这可能会或可能不会抛出异常。”

如果它偶尔不会抛出异常,你也可能永远不会抛出异常。这样,你至少是一致的。

答案 2 :(得分:0)

Herb Sutter正在讨论当类T的对象中存在未捕获的异常时,类U的对象被销毁的情况。 std::uncaught_exception()会在true析构函数中返回T。析构函数将无法确定在堆栈展开期间是否调用它。如果是的话,它一定不能抛出,否则就像往常一样。

U在析构函数中使用类T时会出现问题。 U会发现自己处理的是一个无用的T对象,它拒绝在析构函数中做任何冒险的事情(可能包括编写日志文件或将事务提交到数据库)。

Herb Sutter建议永远不要扔掉析构函数,这是一个好主意。但是,C ++ 17提供了另一种选择。它引入了std::uncaught_exceptions(),可用于确定析构函数是否可以抛出。以下示例显示了在C ++ 14模式下是否符合的问题。如果在C ++ 17模式下编译,它将正常工作。


#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;
}