【问题标题】:Why is the destructor called for an object that is not deleted?为什么要为未删除的对象调用析构函数?
【发布时间】:2019-01-29 04:23:21
【问题描述】:
struct A
{
    ~A() = delete;
};

int main()
{
    new A{};
}

编译失败并显示错误信息:

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

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

编译 GCC 8.1.0

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

【问题讨论】:

标签: c++ language-lawyer


【解决方案1】:

这是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();,就本规则而言,它们具有相同的含义。

【讨论】:

  • 当其中一个元素抛出构造函数时,您错过了为数组元素调用析构函数的情况(在其他答案中广泛讨论)
  • @SergeyA 根据标准,即使构造函数是 no 除了可能调用析构函数。
  • @Oliv,当然,你在你的答案中钉了它。我只是向巴里展示一个(简化的)案例。
  • @Barry,很公平,您指的是一个具体案例。具有讽刺意味的是,看起来它可能是相关的:)
  • @Barry 另一方面,既不是静态存储时长也不是线程存储时长,...在这里创建!!
【解决方案2】:

这里可能是一个 gcc 错误。

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

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

强调我的

gcc 在创建非数组时也应用此规则,这隐含地不是标准规则。 多亏了下面的 cmets,gcc 似乎正好相反:在创建非数组时,它考虑析构函数可能会被调用,并且在创建数组时它只是不检查析构函数。

【讨论】:

  • 从技术上讲,引用的规则并没有说示例程序不是格式错误的。这似乎是一个很好的提示,说明了为什么会出现这个错误。
  • @RicharCritten 在这种情况下,clang 拒绝代码:godbolt.org/z/xMMrKh。也许只是一个额外的! gcc 代码中的字符!!
  • 是的,看起来像个虫子
  • new A[10] 编译的事实令人费解,因为如果A()throws 怎么办?在这种情况下生成的代码假装不需要调用析构函数,这是不正确的。看起来很像 gcc 中的错误。
  • Live with A() throwing: godbolt.org/z/7f7CFG 怎么样,因为A POD 不需要析构函数?使 A 非 POD 会产生预期的错误:godbolt.org/z/7P-4Ph
【解决方案3】:

据我所知,示例中没有销毁任何对象,如果将表达式更改为new A;,它恰好可以编译

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


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

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

隐式调用析构函数

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

...析构函数 也通过使用删除表达式 (5.3.5) 来隐式调用由 新表达式(5.3.4);调用的上下文是删除表达式。 [注:类数组 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 节都没有。包含适用于这种情况的规则,并且不调用析构函数,也没有删除表达式。因此析构函数不会被潜在地调用,所以它的格式很好,可以删除析构函数。

【讨论】:

  • language-lawyer 中没有足够的答案,抱歉。您需要添加更多 Standarteese 以使其成为此标签中的正确答案。
  • @SergeyA 我从 [class.dtor] 中查找了我认为最相关的标准规则。
猜你喜欢
  • 2018-05-24
  • 1970-01-01
  • 2015-01-15
  • 2017-05-18
  • 1970-01-01
  • 2019-11-16
  • 2010-10-16
  • 2011-03-23
  • 1970-01-01
相关资源
最近更新 更多