为什么析构函数需要一个未删除的对象?

时间:2022-09-06 00:20:58
struct A
{
    ~A() = delete;
};

int main()
{
    new A{};
}

This fails to compile with error message:

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

error: use of deleted function 'A::~A()' new A{};

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

As I understand I'm not destroying the object so why is it trying to call the destructor?

据我所知,我没有破坏对象所以为什么要尝试调用析构函数?

Compiled with GCC 8.1.0

用GCC 8.1.0编译

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

3 个解决方案

#1


34  

This is gcc bug 57082.

这是gcc bug 57082。


Let's go from the bottom up.

让我们自下而上。

[dcl.fct.def.delete]/2:

[dcl.fct.def.delete] / 2:

A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.

除了声明它之外,隐式或显式引用已删除函数的程序是不正确的。

Clearly, we're not referring to ~A() explicitly. Are we referring to it implicitly? [class.dtor]/12:

显然,我们没有明确提到~A()。我们是否隐含地提到它? [class.dtor] / 12:

A destructor is invoked implicitly

隐式调用析构函数

  • for a constructed object with static storage duration ([basic.stc.static]) at program termination ([basic.start.term]),
  • 对于在程序终止时具有静态存储持续时间([basic.stc.static])的构造对象([basic.start.term]),
  • for a constructed object with thread storage duration ([basic.stc.thread]) at thread exit,
  • 对于在线程出口处具有线程存储持续时间([basic.stc.thread])的构造对象,
  • for a constructed object with automatic storage duration ([basic.stc.auto]) when the block in which an object is created exits ([stmt.dcl]),
  • 对于具有自动存储持续时间([basic.stc.auto])的构造对象,当创建对象的块退出时([stmt.dcl]),
  • for a constructed temporary object when its lifetime ends ([conv.rval], [class.temporary]).
  • 对于构造的临时对象,它的生命周期结束([conv.rval],[class.temporary])。

Or in [expr.new]/20:

或者在[expr.new] / 20中:

If the new-expression creates an array of objects of class type, the destructor is potentially invoked.

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

Do we have any of those things? No, there is no object with automatic, static, or thread storage duration here, nor is there a constructed temporary object, nor is our new-expression creating an array. There is only one object here at all, the one A with dynamic storage duration that we're aggregate-initializing.

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

Since we're neither explicitly nor implicitly referring to ~A(), we can't be tripping over that rule. Hence, gcc bug. Note also that gcc accepts new A; and new A();, which have the same meaning as far as this rule is concerned.

既然我们既没有明确地也没有暗示地引用~A(),我们就不能绊倒那条规则。因此,gcc bug。另请注意,gcc接受新的A;和新的A();,就这个规则而言具有相同的含义。

#2


4  

Probably a gcc bug here.

可能是这里的gcc bug。

The standard specifies that the destructor is potentially invoked when the new expression create an array [expr.new]:

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

If the new-expression creates an object or an array of objects of class type, access and ambiguity control are done for the allocation function, the deallocation function, and the constructor. If the new-expression creates an array of objects of class type, the destructor is potentially invoked.

如果new-expression创建一个对象或类类型的对象数组,则对分配函数,释放函数和构造函数进行访问和歧义控制。如果new-expression创建类类型的对象数组,则可能会调用析构函数。

emphasis mine

强调我的

gcc applies also this rule when creating a non array, which is implicitly not a standard rule. Thanks to below comments it seems gcc do the exact opposite: when creating a non array, it considers the destructor to be potentialy invoked and when creating an array it just don't check the destructor.

gcc在创建非数组时也应用此规则,该数组隐式不是标准规则。由于下面的注释,似乎gcc正好相反:当创建一个非数组时,它认为析构函数是可能被调用的,并且在创建数组时它只是不检查析构函数。

#3


2  

As far as I can tell, no objects are destroyed in the example, and it happens to compile if the expression is changed to new A;

据我所知,在示例中没有销毁任何对象,如果表达式更改为新的A,它就会编译;

I think that the example code not compiling is is a bug in GCC. Clang compiles it just fine.

我认为没有编译的示例代码是GCC中的一个错误。 Clang编译得很好。


Answer for the newly added language-lawyer tag.

回答新添加的语言律师标签。

The crucial standard rule is this in [class.dtor]:

关键的标准规则是[class.dtor]:

A destructor is invoked implicitly

隐式调用析构函数

... cases that don't apply involving other storage durations than dynamic ...

......不适用于涉及其他存储时间而非动态的情况......

... A destructor is also invoked implicitly through use of a delete-expression (5.3.5) for a constructed object allocated by a new-expression (5.3.4); the context of the invocation is the delete-expression. [ Note: An array of class type contains several subobjects for each of which the destructor is invoked. — end note ] A destructor can also be invoked explicitly. A destructor is potentially invoked if it is invoked or as specified in 5.3.4, 12.6.2, and 15.1.

...对于由new-expression(5.3.4)分配的构造对象,还通过使用delete-expression(5.3.5)隐式调用析构函数;调用的上下文是delete-expression。 [注意:类类型数组包含几个子对象,每个子对象都调用析构函数。 - 结束注释]也可以显式调用析构函数。如果调用析构函数或在5.3.4,12.6.2和15.1中指定,则可能会调用析构函数。

5.3.4 is [expr.new] which only specifies

5.3.4是[expr.new],它只指定

... If the new-expression creates an array of objects of class type, the destructor is potentially invoked (12.4).

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

which doesn't apply.

这不适用。

12.6.2 is [class.base.init] which only specifies

12.6.2是[class.base.init],它只指定

In a non-delegating constructor, the destructor for each potentially constructed subobject of class type is potentially invoked (12.4).

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

Which doesn't apply

哪个不适用

15.1 is [except.throw] which specifies how an exception object is destroyed, which doesn't apply

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

Conclusion: None of sections 5.3.4, 12.6.2, and 15.1. contain a rule that applies to this case, and the destructor isn't invoked, nor is there a delete-expression. Therefore the destructor isn't potentially invoked, so it is well formed for the destructor to be deleted.

结论:5.3.4,12.6.2和15.1节都没有。包含适用于这种情况的规则,并且不调用析构函数,也没有delete-expression。因此,不会潜在地调用析构函数,因此很好地删除了析构函数。

#1


34  

This is gcc bug 57082.

这是gcc bug 57082。


Let's go from the bottom up.

让我们自下而上。

[dcl.fct.def.delete]/2:

[dcl.fct.def.delete] / 2:

A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.

除了声明它之外,隐式或显式引用已删除函数的程序是不正确的。

Clearly, we're not referring to ~A() explicitly. Are we referring to it implicitly? [class.dtor]/12:

显然,我们没有明确提到~A()。我们是否隐含地提到它? [class.dtor] / 12:

A destructor is invoked implicitly

隐式调用析构函数

  • for a constructed object with static storage duration ([basic.stc.static]) at program termination ([basic.start.term]),
  • 对于在程序终止时具有静态存储持续时间([basic.stc.static])的构造对象([basic.start.term]),
  • for a constructed object with thread storage duration ([basic.stc.thread]) at thread exit,
  • 对于在线程出口处具有线程存储持续时间([basic.stc.thread])的构造对象,
  • for a constructed object with automatic storage duration ([basic.stc.auto]) when the block in which an object is created exits ([stmt.dcl]),
  • 对于具有自动存储持续时间([basic.stc.auto])的构造对象,当创建对象的块退出时([stmt.dcl]),
  • for a constructed temporary object when its lifetime ends ([conv.rval], [class.temporary]).
  • 对于构造的临时对象,它的生命周期结束([conv.rval],[class.temporary])。

Or in [expr.new]/20:

或者在[expr.new] / 20中:

If the new-expression creates an array of objects of class type, the destructor is potentially invoked.

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

Do we have any of those things? No, there is no object with automatic, static, or thread storage duration here, nor is there a constructed temporary object, nor is our new-expression creating an array. There is only one object here at all, the one A with dynamic storage duration that we're aggregate-initializing.

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

Since we're neither explicitly nor implicitly referring to ~A(), we can't be tripping over that rule. Hence, gcc bug. Note also that gcc accepts new A; and new A();, which have the same meaning as far as this rule is concerned.

既然我们既没有明确地也没有暗示地引用~A(),我们就不能绊倒那条规则。因此,gcc bug。另请注意,gcc接受新的A;和新的A();,就这个规则而言具有相同的含义。

#2


4  

Probably a gcc bug here.

可能是这里的gcc bug。

The standard specifies that the destructor is potentially invoked when the new expression create an array [expr.new]:

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

If the new-expression creates an object or an array of objects of class type, access and ambiguity control are done for the allocation function, the deallocation function, and the constructor. If the new-expression creates an array of objects of class type, the destructor is potentially invoked.

如果new-expression创建一个对象或类类型的对象数组,则对分配函数,释放函数和构造函数进行访问和歧义控制。如果new-expression创建类类型的对象数组,则可能会调用析构函数。

emphasis mine

强调我的

gcc applies also this rule when creating a non array, which is implicitly not a standard rule. Thanks to below comments it seems gcc do the exact opposite: when creating a non array, it considers the destructor to be potentialy invoked and when creating an array it just don't check the destructor.

gcc在创建非数组时也应用此规则,该数组隐式不是标准规则。由于下面的注释,似乎gcc正好相反:当创建一个非数组时,它认为析构函数是可能被调用的,并且在创建数组时它只是不检查析构函数。

#3


2  

As far as I can tell, no objects are destroyed in the example, and it happens to compile if the expression is changed to new A;

据我所知,在示例中没有销毁任何对象,如果表达式更改为新的A,它就会编译;

I think that the example code not compiling is is a bug in GCC. Clang compiles it just fine.

我认为没有编译的示例代码是GCC中的一个错误。 Clang编译得很好。


Answer for the newly added language-lawyer tag.

回答新添加的语言律师标签。

The crucial standard rule is this in [class.dtor]:

关键的标准规则是[class.dtor]:

A destructor is invoked implicitly

隐式调用析构函数

... cases that don't apply involving other storage durations than dynamic ...

......不适用于涉及其他存储时间而非动态的情况......

... A destructor is also invoked implicitly through use of a delete-expression (5.3.5) for a constructed object allocated by a new-expression (5.3.4); the context of the invocation is the delete-expression. [ Note: An array of class type contains several subobjects for each of which the destructor is invoked. — end note ] A destructor can also be invoked explicitly. A destructor is potentially invoked if it is invoked or as specified in 5.3.4, 12.6.2, and 15.1.

...对于由new-expression(5.3.4)分配的构造对象,还通过使用delete-expression(5.3.5)隐式调用析构函数;调用的上下文是delete-expression。 [注意:类类型数组包含几个子对象,每个子对象都调用析构函数。 - 结束注释]也可以显式调用析构函数。如果调用析构函数或在5.3.4,12.6.2和15.1中指定,则可能会调用析构函数。

5.3.4 is [expr.new] which only specifies

5.3.4是[expr.new],它只指定

... If the new-expression creates an array of objects of class type, the destructor is potentially invoked (12.4).

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

which doesn't apply.

这不适用。

12.6.2 is [class.base.init] which only specifies

12.6.2是[class.base.init],它只指定

In a non-delegating constructor, the destructor for each potentially constructed subobject of class type is potentially invoked (12.4).

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

Which doesn't apply

哪个不适用

15.1 is [except.throw] which specifies how an exception object is destroyed, which doesn't apply

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

Conclusion: None of sections 5.3.4, 12.6.2, and 15.1. contain a rule that applies to this case, and the destructor isn't invoked, nor is there a delete-expression. Therefore the destructor isn't potentially invoked, so it is well formed for the destructor to be deleted.

结论:5.3.4,12.6.2和15.1节都没有。包含适用于这种情况的规则,并且不调用析构函数,也没有delete-expression。因此,不会潜在地调用析构函数,因此很好地删除了析构函数。