转换到C ++ 11,其中使用noexcept隐式声明析构函数

时间:2015-10-05 19:26:40

标签: c++ c++11 exception-handling destructor exception-specification

在C ++ 11中,没有任何异常规范的析构函数使用if隐式声明,这是对C ++ 03的更改。因此,用于从C ++ 03中的析构函数抛出的代码仍然可以在C ++ 11中正常编译,但是一旦尝试从这样的析构函数抛出就会在运行时崩溃。

由于这样的代码没有编译时错误,如何安全地转换到C ++ 11,而不是将代码库中的所有现有析构函数声明为noexcept ,这将是非常过于冗长和干扰,或检查每个析构函数是否可能抛出,这将非常耗时且容易出错,或者在运行时捕获并修复所有崩溃,这无法保证发现所有这些情况?

3 个答案:

答案 0 :(得分:7)

请注意,这些规则实际上并不残酷。如果隐式声明的析构函数是析构函数,则析构函数只会隐式noexcept。因此,将至少一个基类或成员类型标记为noexcept (false)将会破坏整个层次结构/聚合的noexcept

#include <type_traits>

struct bad_guy
{
  ~bad_guy() noexcept(false) { throw -1; }
};

static_assert(!std::is_nothrow_destructible<bad_guy>::value,
              "It was declared like that");

struct composing
{
  bad_guy member;
};

static_assert(!std::is_nothrow_destructible<composing>::value,
              "The implicity declared d'tor is not noexcept if a member's"
              " d'tor is not");

struct inheriting : bad_guy
{
  ~inheriting() { }
};

static_assert(!std::is_nothrow_destructible<inheriting>::value,
              "The d'tor is not implicitly noexcept if an implicitly"
              " declared d'tor wouldn't be.  An implicitly declared d'tor"
              " is not noexcept if a base d'tor is not.");

struct problematic
{
  ~problematic() { bad_guy {}; }
};

static_assert(std::is_nothrow_destructible<problematic>::value,
              "This is the (only) case you'll have to look for.");

尽管如此,我同意Chris Beck你迟早要摆脱你的投掷破坏者。它们还可以使您的C ++ 98程序在最不方便的时候爆炸。

答案 1 :(得分:4)

正如5gon12eder所提到的,有一些规则会导致没有异常规范的析构函数被隐式声明为noexceptnoexcept(false)。如果您的析构函数可能会抛出并且您将其留给编译器来决定其异常规范,那么您将使用轮盘赌,因为您依赖于编译器的决定受到祖先和类成员的影响,以及他们的祖先和成员递归,这些太复杂,无法跟踪,并且在代码演变过程中可能会发生变化。因此,在定义具有可抛出的主体的析构函数时,并且没有异常规范时,必须将其显式声明为noexcept(false)。另一方面,如果您确定主体可能不会抛出,您可能希望声明它noexcept更明确并帮助编译器进行优化,但是如果您选择这样做则要小心,因为如果你的类的任何成员/祖先的析构函数决定抛出,你的代码将在运行时中止。

请注意,任何隐式定义的具有空体的析构函数或析构函数都不会产生任何问题。如果所有成员和祖先的所有析构函数都是noexcept,则它们只是隐式noexcept

进行转换的最佳方法是找到所有具有非空体并且没有异常规范的析构函数,并声明每个可能使用noexcept(false)抛出的析构函数。请注意,您只需要检查析构函数的主体 - 它所做的任何立即抛出或它所调用的函数所做的任何抛出,递归。不需要检查具有空体的析构函数,具有现有异常规范的析构函数或任何隐式定义的析构函数。在实践中,没有那么多的东西需要检查,因为这些的普遍用途只是释放资源。

因为我回答自己,这正是我最终在我的案件中所做的事情,毕竟这并不是那么痛苦。

答案 2 :(得分:3)

我曾经经历过同样的困境。

基本上我得出的结论是,接受这些毁灭者投掷而只是生活在后果中的事实通常比经历使他们不投掷的痛苦更糟糕。

原因是当你抛出析构函数时,你冒着更加不稳定和不可预测状态的风险。

作为一个例子,我曾经在一个项目上工作过一次,由于各种原因,一些开发人员在项目的某些部分使用了流量控制的例外,并且它在多年来都运行良好。后来,有人注意到在项目的不同部分,有时客户端无法发送它应该发送的一些网络消息,因此他们创建了一个RAII对象,该对象将在其析构函数中发送消息。有时网络会抛出异常,所以这个RAII析构函数会抛出,但是谁在乎呢?它没有记忆清理所以它不是泄漏。

这可以在99%的情况下正常工作,除非异常流控制路径碰巧穿过网络,然后也会引发异常。然后,你有两个实时异常被同时解开,所以“用你死了”,用C ++常见问题解答中不朽的话语。

老实说,当析构函数抛出时,我宁愿让程序立即终止,所以我们可以与编写抛出析构函数的人进行交谈,而不是试图维护一个故意抛出析构函数的程序,这是委员会的共识/社区似乎。所以他们做了这个突破性的改变,以帮助你断言你的析构函数是好的而不是抛出。如果您的遗留代码库中存在大量黑客攻击可能需要做很多工作,但如果您想继续开发并维护它,至少在C ++ 11标准上,您可能最好去做清理工作破坏者。

底线:

你是对的,你真的不希望保证找到抛出析构函数的所有可能实例。因此,可能会出现一些情况,当您的代码在C ++ 11中编译时,如果它不符合C ++ 98标准,它将崩溃。但总的来说,清理析构函数并以C ++ 11运行可能会比使用旧标准的抛出析构函数稳定得多。