47
#include <iostream>

using namespace std;

struct A
{
    A() { cout << "A" << endl; }
    ~A() { cout << "~A" << endl; }
};

A Ok() { return {}; }
A NotOk() { throw "NotOk"; }

struct B
{
    A a1;
    A a2;
};

void f(B) {}

int main()
{
    try
    {
        f({ Ok(), NotOk() });
    }
    catch (...)
    {}
}

vc++clang输出:

A
~A

虽然gcc输出:

A

这似乎是 GCC 的一个严重错误。

有关参考,请参阅GCC 错误 66139Andrzej Krzemieński 的“GCC 中的严重错误”

我只是好奇:

C++ 标准是否保证统一初始化是异常安全的?

4

1 回答 1

31

似乎是这样:

奇怪地在所有地方的 §6.6/2 Jump Statements [stmt.jump] 中找到(N4618):

从范围退出时(无论如何完成),已在该范围内构建的具有自动存储持续时间 (3.7.3) 的对象将按照其构建的相反顺序销毁。[注:对于临时性,见 12.2。—结束注释] 转移出循环,转移出块,或回过具有自动存储持续时间的初始化变量涉及销毁具有自动存储持续时间的对象,这些对象在转移点的范围内但不在转移点的范围内. (参见 6.7 中的转移到块)。[注意:但是,程序可以终止(例如通过调用std::exit()std::abort()(18.5)),而不会破坏具有自动存储持续时间的类对象。——尾注]

我认为这里的重点是“(无论如何完成)”部分。这包括一个异常(但不包括导致 a 的事物std::terminate)。


编辑

我认为更好的参考是§15.2/3 Constructors and destructors [except.ctor](强调我的):

如果除委托构造函数之外的对象的初始化或销毁因异常而终止,则为对象的每个直接子对象调用析构函数,对于完整对象,调用已完成初始化的虚拟基类子对象 (8.6) 和其析构函数尚未开始执行,除了在销毁的情况下,不会销毁类联合类的变体成员。子对象按照其构建完成的相反顺序被销毁。这种破坏在进入构造函数或析构函数的函数尝试块的处理程序之前排序 ,如果有的话。

这将包括聚合初始化(我今天学到的可以称为非空初始化

...对于具有构造函数的对象,我们可以引用 §12.6.2/12 [class.base.init](强调我的):

在非委托构造函数中,每个可能构造的类类型子对象的析构函数都可能被调用(12.4)。[注意:此规定确保可以为完全构造的子对象调用析构函数,以防引发异常(15.2)。——尾注]

于 2017-04-28T12:31:57.843 回答