为什么析构函数要求未删除的对象?

时间:2018-08-22 20:40:55

标签: c++ language-lawyer

struct A
{
    ~A() = delete;
};

int main()
{
    new A{};
}

这无法编译并显示错误消息:

  

错误:使用已删除的函数'A ::〜A()'             新的A {};

据我了解,我并没有销毁该对象,为什么它要尝试调用析构函数?

编译为 GCC 8.1.0

g++ -std=c++17 -O2

3 个答案:

答案 0 :(得分:37)

这是gcc bug 57082


让我们从头开始。

[dcl.fct.def.delete]/2

  

除声明其内容外,隐式或显式引用已删除函数的程序格式错误。

很显然,我们不是在明确引用~A()。我们是否暗指它? [class.dtor]/12

  

析构函数被隐式调用

     
      
  • 对于在程序终止([basic.start.term])具有静态存储持续时间([basic.stc.static])的构造对象,
  •   
  • 对于在线程出口具有线程存储持续时间([basic.stc.thread])的构造对象,
  •   
  • 对于在创建对象的块退出([stmt.dcl])时具有自动存储持续时间([basic.stc.auto])的构造对象,
  •   
  • 用于构造的临时对象的生命周期结束时([conv.rval],[class.temporary])。
  •   

或在[expr.new]/20中:

  

如果 new-expression 创建一个类类型的对象数组,则可能会调用析构函数。

我们有这些东西吗?不,这里没有对象具有自动,静态或线程存储持续时间,也没有构造的临时对象,我们的 new-expression 也没有创建数组。此处只有一个对象,一个A,具有动态存储持续时间,我们正在对其进行初始化。

由于我们既没有明确地也没有隐含地指称~A(),所以我们不能违反该规则。因此,gcc错误。另请注意,gcc接受new A;new A();,就此规则而言,它们具有相同的含义。

答案 1 :(得分:4)

这里可能是gcc错误。

该标准指定在新表达式创建数组[expr.new]时潜在地调用析构函数:

  

如果new表达式创建一个类类型的对象或对象数组,则将对分配函数,解除分配函数和构造函数进行访问和模糊控制。   如果new表达式创建了一个类类型的对象数组,则可能会调用析构函数。

强调我的

gcc在创建非数组时也适用此规则,这显然不是标准规则。由于以下注释,gcc似乎完全相反:在创建非数组时,它考虑了析构函数被潜在地调用,并且在创建数组时只是不检查析构函数。

答案 2 :(得分:2)

据我所知,该示例中没有破坏任何对象,并且如果将表达式更改为new A;

我认为未编译的示例代码是GCC中的错误。 Clang compiles it just fine


回答新添加的language-lawyer标签。

关键标准规则在[class.dtor]中是这样的:

  

析构函数被隐式调用

     

...不适用的情况涉及动态存储以外的其他存储持续时间...

     

...破坏者   对于由分配的构造对象,还可以通过使用delete-expression(5.3.5)隐式调用   一个new表达式(5.3.4);调用的上下文是delete-expression。 [注意:类的数组   type包含几个子对象,每个子对象都会调用析构函数。 —尾注]析构函数可以   也可以显式调用。如果析构函数被调用或按照5.3.4、12.6.2,   和15.1。

5.3.4是[expr.new],仅指定

  

...如果   new-expression创建一个类类型为对象的数组,可能会调用析构函数(12.4)。

这不适用。

12.6.2是[class.base.init],仅指定

  

在非委托构造函数中,类类型的每个可能构造的子对象的析构函数为   可能被调用(12.4)。

哪些不适用

15.1是[except.throw],它指定如何销毁异常对象,该对象不适用

结论:第5.3.4、12.6.2和15.1节均未涉及。包含适用于这种情况的规则,并且不会调用析构函数,也没有delete-expression。因此,析构函数不会被调用,因此可以很好地删除析构函数。