代码之家  ›  专栏  ›  技术社区  ›  xmh0511

当前标准是否保证在初始化完整对象引发异常时调用子对象的析构函数

  •  1
  • xmh0511  · 技术社区  · 5 年前

    分段 except.ctor 规则规定:

    1. 当控件从抛出异常的点传递到处理程序时 自动存储持续时间 由本子条款中指定的称为堆栈展开的过程销毁。
    2. [...]
    3. 如果通过委托构造函数以外的方式终止对对象的初始化或销毁,则会为该对象的每个直接子对象调用析构函数,对于完整的对象,会为其初始化已完成([dcl.init])且其析构函数尚未开始执行的虚拟基类子对象调用析构函数,除了在销毁的情况下,类联合的变体成员不会被销毁。[注意:如果这样一个对象的引用成员延长了临时对象的生存期,那么引用成员的生存期就结束了,因此临时对象的生存期实际上不会延长。结束注意]子对象的销毁顺序与其构造完成顺序相反。在进入构造函数或析构函数(如果有的话)的函数try块的处理程序之前,会对这种销毁进行排序。

    根据bullet 3,这些状态也可以被视为堆栈展开过程的一部分。因为第一条规则听起来像是堆栈展开的调用只针对具有 自动存储持续时间 .该堆栈展开不会调用具有动态存储持续时间或其他持续时间的对象吗?

    #include <iostream>
    struct A{
      ~A(){
        std::cout<<"invoke\n";
      }
    };
    struct B{
     B(){
       throw 0;
     }
      A a;
    };
    int main(){
       try{  
          auto ptr = new B{};
        }catch(int){
       }
    }
    

    显然,子对象 a 占用具有 动态存储持续时间 在我看来,A的析构函数应该被调用,因为它是在B的初始化过程中完全构造的。GCC和Clang都同意这一点 that .它是这些编译器的扩展吗?如何解读?

    1 回复  |  直到 5 年前
        1
  •  2
  •   Brian Bi    5 年前

    按照[except.ctor]/3的简单表述,它适用于具有任何存储持续时间的对象,只要它们是其构造因异常而终止的对象的子对象。这不应该引起争议。

    然而,[except.ctor]的措辞随着时间的推移发生了变化,这似乎产生了OP已经注意到的一些问题。背景是,过去的措辞与当前的措辞类似,“堆栈展开”只是指自动对象的销毁,有人注意到这造成了不一致,当没有找到匹配的处理程序时,无法保证是否发生堆栈展开,但 保证子对象销毁发生(因为文本中没有任何内容表明如果找不到处理程序可能不会发生)。这是 CWG 1774 .CWG同意这种不一致是不可取的(也许这是无意的,尽管该页面没有说明)。因此,措辞发生了变化,子对象销毁将是“堆栈展开”的一个方面,因此包含在[except.handle]/9中,而不是作为一个单独的过程。但后来,为了解决 a different DR 这与例外情况无关,旧的措辞被重新添加,我90%确信这是无意的。CWG2256决议的目的只是为了避免对琐碎的可破坏对象的歧视,而不是真正改变异常处理的任何内容。

    因此,当前的措辞是有缺陷的,该标准仍应被解读为“堆栈展开”包括子对象销毁( ,CWG 1774的决议生效)。你甚至可以通过编辑来解决这个问题( ,针对标准源提交拉取请求)。