Bri*_*ian 12 c++ language-lawyer
该标准区分了抛出异常时发生的两种破坏形式.强调我的.
§15.2/ 1
当控制从throw-expression传递到处理程序时,将为输入try块后构造的所有自动对象调用析构函数.自动对象按照完成构造的相反顺序销毁.
§15.2/ 2
任何存储持续时间的对象,其初始化或销毁由异常终止,将为其所有完全构造的子对象(不包括类似联合的类的变体成员)执行析构函数,即对于其主要构造函数的子对象( 12.6.2)已完成执行并且析构函数尚未开始执行.类似地,如果对象的非委托构造函数已完成执行并且该对象的委托构造函数以异常退出,则将调用该对象的析构函数.如果对象是在new-expression中分配的,则调用匹配的释放函数(3.7.4.2,5.3.4,12.5)(如果有)以释放对象占用的存储空间.
§15.2/ 3
为从try块到throw-expression的路径构造的自动对象调用析构函数的过程 称为" 堆栈展开"."如果在堆栈展开期间调用的析构函数以异常退出,
std::terminate则调用(15.5.1).[ 注意:因此析构函数通常应该捕获异常,而不是让它们从析构函数中传播出来.- 结束说明 ]
因此,似乎我们有(a)堆栈展开,它会破坏自动对象,以及(b)破坏构造函数或析构函数通过异常退出的对象的完全构造的子对象,无论存储持续时间如何都会发生.
仔细阅读§15.2/ 1表明,只有当控制传递给处理程序时,才会发生堆栈展开,如果未处理异常,则可能会发生堆栈展开的可能性.的确,§15.5.2/ 2说,
在没有找到匹配处理程序的情况下,无论堆栈是否在
std::terminate()被调用之前被展开,它都是实现定义的."
但是§15.2/ 2的措辞似乎没有留下这种可能性.它只是说初始化或破坏必须由异常终止 - 而不是控件必须传递给处理程序.所以我的解释是,即使没有处理异常,子对象仍然被销毁.这是正确的解释吗?
例如,假设我们有
std::vector<int> V;
ComplicatedObject* p = new ComplicatedObject();
Run Code Online (Sandbox Code Playgroud)
并且它ComplicatedObject的构造函数抛出,并且不处理异常.然后是否V被销毁是实现定义的.它是否也是实现定义的,是否*p销毁了完全构建的子对象?请注意,此类对象没有自动存储持续时间.
您的解释(显然)是正确的,在这种情况下,Clang和GCC都不符合标准.
这是CWG问题#1774的主题:
当异常处理导致调用时,15.5.1 [except.terminate]第2段的当前措辞为实现提供了很大的自由度
std::terminate:在没有找到匹配处理程序的情况下[..]
这与15.2 [除外]第2段中通过委托构造构建的子对象和对象的处理形成对比:
任何存储持续时间的对象[...]
这里必须调用析构函数.如果这些要求得到统一,将会有所帮助.
已提出一项决议,但未将其纳入C++ 14.您的引用§15.3/ 11将被删除.相反,§15.2将包含
对于任何存储持续时间的类类型的对象,其初始化或销毁由异常终止,对每个对象的完全构造的子对象调用析构函数,即对于主构造函数(12.6.2)具有的每个子对象.完成执行并且析构函数尚未开始执行,除了在破坏的情况下,类型联合类的变体成员不会被销毁.子对象以完成构造的相反顺序销毁.在进入构造函数或析构函数的函数try-block的处理程序(如果有的话)之前,对这种破坏进行排序.
这应该消除任何疑虑.另请注意,这些更改已纳入当前的工作草案N4296.