投掷我的析构函数时,为什么总是在"抛出一个......的实例后终止"?

Ale*_*lke 6 c++ destructor g++ exception throw

我正在尝试编写一个单元测试来检测我的类的lock()功能的无效使用.为了做到这一点,我想使用析构函数并从那里抛出一个异常.不幸的是,g ++决定调用std :: terminate()而不是捕获异常.

该类有一个非常简化的版本:

class A
{
public:
    A() : f_lock(0) {}
    ~A() { if(f_lock) throw my_exception("still locked"); }
    lock() { ++f_lock; }
    unlock() { --f_lock; }
private:
    int f_lock;
};
Run Code Online (Sandbox Code Playgroud)

有一个有效的测试:

A *a = new A;
a->lock();
...
a->unlock();
delete a;
Run Code Online (Sandbox Code Playgroud)

我正在尝试编写无效测试:

A *a = new A;
a->lock();
...
bool success = false;
try
{
    delete a;
}
catch(my_exception const&)
{
    success = true;
}
catch(...)
{
    // anything else is a failure
}
if(!success)
{
    // test failed...
    CPPUNIT_ASSERT(!"test failed");
}
Run Code Online (Sandbox Code Playgroud)

现在,即使在另一个异常处于活动状态时未调用throw,也会delete调用这些调用std::terminate().(即std::uncaught_exception()是假的.)而且我也清楚地抓住所有例外!

我做错了什么,或者被g++编程为总是在析构函数中做到这一点?


更新:

以下评论中的dyp答案有效!以下不直接致电std::terminate():

    ~A() noexcept(false) { throw ...; }
Run Code Online (Sandbox Code Playgroud)

另外,为了参考为什么你不想抛出析构函数,这个页面非常好;

https://www.securecoding.cert.org/confluence/display/cplusplus/ERR33-CPP.+Destructors+must+not+throw+exceptions


为了澄清,有完整版的析构函数.正如我们所看到的,我首先发布一条消息(它通常在您的控制台中,也可能会发送到日志中).其次,我确保我们还没有管理例外.最后,我抛出一个名为的异常exception_exit,terminate()虽然在GUI应用程序中你可能希望显示某种类型的MessageBox让用户知道发生的事情(因为你可以捕获Message,你可以向用户显示它) )然后强制关闭应用程序.

Node::~Node() noexcept(false)
{
    if(f_lock > 0)
    {
        // Argh! A throw in a destructor... Yet this is a fatal
        // error and it should never ever happen except in our
        // unit tests to verify that it does catch such a bug
        Message msg(message_level_t::MESSAGE_LEVEL_FATAL, err_code_t::AS_ERR_NOT_ALLOWED);
        msg << "a node got deleted while still locked.";

        // for security reasons, we do not try to throw another
        // exception if the system is already trying to process
        // an existing exception
        if(std::uncaught_exception())
        {
            // still we cannot continue...
            std::abort();
        }

        throw exception_exit(1, "a node got deleted while still locked.");
    }
}
Run Code Online (Sandbox Code Playgroud)

另外,另一个细节是,您应该使用该NodeLock对象来管理f_lock标志.这是异常安全的,因为它使用RAII(即范围锁定).但是,此时我不想强迫用户使用NodeLock来锁定/解锁节点,因此在析构函数中进行此测试.

M.M*_*M.M 5

在 C++11 中noexcept添加了关键字。这可以用在函数异常规范中:

\n\n
    \n
  • noexcept(true)与 相同,即如果抛出任何内容,throw()则此函数terminate
  • \n
  • noexcept(false)意味着该函数可能会抛出任何东西
  • \n
\n\n

对于大多数函数,它们没有异常规范,除非您给它们一个。没有异常规范的函数可能会抛出任何异常。

\n\n

不过,析构函数有一种特殊情况,可以在 C++11 [class.dtor]/3 中找到:

\n\n
\n

没有异常规范的析构函数的声明被隐式地认为具有与隐式声明相同的异常规范(15.4)。

\n
\n\n

引用的规则 15.4 规定,隐式声明的特殊成员函数始终具有异常规范。该规范由以下规则确定,[except.spec]/14:

\n\n
\n

隐式声明的特殊成员函数(第 12 条)应具有异常规范。如果f是隐式声明的默认构造函数、复制构造函数、移动构造函数、析构函数、复制赋值运算符或移动赋值运算符,T当且仅当函数的异常规范T直接允许时,其隐式异常规范指定类型 ID由 \xe2\x80\x99s 隐式定义调用;如果它直接调用的任何函数允许所有异常,则应允许所有异常;如果它直接调用的每个函数都不允许异常,则应不允许异常。fff

\n
\n\n

本节中的“它直接调用的函数”是指任何成员变量或基类的析构函数(递归应用)。如果没有这样的函数,那么这样的函数就不允许出现异常,所以默认是noexcept(true)

\n\n

我们可以将上述引用中与您的代码相关的部分总结如下:

\n\n
    \n
  • 如果所有子对象没有析构函数,或者隐式生成的析构函数,或者声明为noexcept(true)或等效的析构函数;那么这个类的析构函数默认为noexcept(true).
  • \n
\n\n

因此,将析构函数更改为 hasnoexcept(false)会重现 C++03 的行为。

\n\n
\n\n

在 C++03 中,这些都没有出现,并且您的析构函数将默认允许所有异常。我不确定为什么在 C++11 中进行此更改,但这可能是因为从析构函数中抛出是一个坏主意,除非您真的知道自己在做什么。

\n