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

Posted

技术标签:

【中文标题】C++ 标准是不是保证统一初始化是异常安全的?【英文标题】:Does the C++ standard guarantee that uniform initialization is exception-safe?C++ 标准是否保证统一初始化是异常安全的? 【发布时间】:2017-04-28 12:15:21 【问题描述】:
#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 bug 66139 和"A serious bug in GCC" by Andrzej Krzemieński。

我只是想知道:

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

【问题讨论】:

什么版本的 GCC 展示了这个? @einpoklum all. 我不认为异常安全是一个问题。除非 1) 通过 exit() 立即退出应用程序,2) 未定义的行为,- 如果对象在自动范围内构造,则必须在执行离开其范围时将其销毁。这是 C++ 的基础。这是一个编译器错误。 首先我认为这可能是一些愚蠢的优化,因为静态分析表明程序会立即在那个包罗万象的处理程序上退出。但不,这是一个编译器错误。 A very major and horrible bug. 所以你有一个博客说这是一个错误,还有一个 gcc 错误报告说并确认它是一个错误......但是你有一个问题问它是否是一个错误? 【参考方案1】:

好像是这样:

在所有地方 (N4618) 的 §6.6/2 跳转语句 [stmt.jump] 中奇怪地发现:

从范围退出时(无论如何完成),具有自动 在该范围内构建的存储期限(3.7.3)是 以它们建造的相反顺序被摧毁。 [注:对于 临时工,见 12.2。 ——尾注] 转移出一个循环,出一个 块,或回过具有自动存储的初始化变量 持续时间涉及使用自动存储销毁对象 在转移点范围内但不在范围内的持续时间 转移到的点。 (参见 6.7 中的转移到块)。 [ 笔记: 但是,可以终止程序(通过调用std::exit()std::abort() (18.5),例如)不破坏类对象 具有自动存储期限。 ——尾注]

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


编辑

我认为更好的参考是 §15.2/3 构造函数和析构函数 [except.ctor](强调我的):

如果初始化或破坏对象不是由 委托构造函数被异常终止,析构函数 为每个对象的直接子对象调用,对于一个 完整对象,虚拟基类子对象,其初始化 已完成 (8.6) 且其析构函数尚未开始执行, 除了在破坏的情况下,a 的变体成员 类似联合的类不会被破坏。子对象在 他们的建设完成的顺序相反。这样的 在进入处理程序之前对销毁进行排序 function-try-block 的构造函数或析构函数,如果有的话。

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

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

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

【讨论】:

现在我正在脑海中回顾我编写的所有程序,这些程序可能会因未捕获的异常和失败的断言而危及系统...... @YSC:嗯,一个失败的断言不应该危及系统。如果一个断言因为这个错误而失败并且一个关键系统被关闭并重新启动而不是提供随机行为,那么我会说这个断言确实完成了它被设计执行的工作。这正是您将其放在那里的原因 - 在面对可怕的错误时保护系统完整性和数据一致性。 @ChristianHackl 我很担心 "[ 注意:但是,可以终止程序(通过调用 std::exit() 或 std::abort() (18.5),对于示例)不会破坏具有自动存储持续时间的类对象。-结束注释]" @YSC:啊,我想我误解了你的评论。然而,在那种情况下,失败的断言应该更不用担心,不是吗?事实上,这就是你使用assertexitabort 的原因;您不想想要执行更多代码,因为您检测到错误或某种紧急情况,其中包括析构函数中的代码。我的观点是:在 C++ 中的某些情况下, 执行本地对象的析构函数而是立即退出是正确且可取的。当然,这个可怕的 GCC 错误不是其中之一。 @ChristianHackl 是的,我同意。我只是从没想过。

以上是关于C++ 标准是不是保证统一初始化是异常安全的?的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot初始教程之统一异常处理

SpringBoot初始教程之统一异常处理

在 C++ 中,给定命名空间范围的 const float 依赖于全局 const float,它们的初始化顺序是不是得到保证?

C++ 是不是对 POD typedef 进行值初始化?

c++ string线程安全吗

重点关注:C++全局变量初始化