【问题标题】:Why is public destructor necessary for mandatory RVO in C++?为什么 C++ 中强制 RVO 需要公共析构函数?
【发布时间】:2021-10-09 22:17:47
【问题描述】:

请考虑如下简单示例,其中函数bar返回一个带有私有析构函数的A类对象,并且必须进行强制返回值优化(RVO):

class A { ~A() = default; };
A bar() { return {}; }

代码被 Clang 接受,但被 GCC 拒绝并出现错误:

error: 'constexpr A::~A()' is private within this context
    2 | A bar() { return {}; }
      |                   ^

https://gcc.godbolt.org/z/q6c33absK

这里有哪一个编译器?

【问题讨论】:

  • VC++ 也接受它。
  • 根据 cppreference 从 C++17 开始,析构函数必须在 return 语句处可访问,这会使 Clang 错误而 GCC 正确。
  • @NathanPierson 听起来像是一个答案
  • @alterigel 更有趣的是展示为什么法律上的答案被编纂。

标签: c++ language-lawyer return-value-optimization


【解决方案1】:

这是CWG 2426。析构函数可能在此上下文中被调用,因为即使在初始化返回 A 对象之后,该函数仍有可能无法成功完成:在 return 语句期间创建的任何临时变量,以及在范围,必须被销毁,如果销毁抛出,那么作为堆栈展开的一部分,A 对象被销毁。编译器应该要求此时可以访问析构函数。

注意 1: 函数最外层作用域的局部变量的析构函数抛出的异常可以被函数 try 块捕获。

注2:在返回对象被销毁后,处理程序被允许执行另一个return语句。标准中有这样的例子。

【讨论】:

  • 此特定上下文(OP 示例)中,简单证明从未调用过析构函数。其他例子不太好。
  • 关于“注 1”:析构函数是否会抛出异常,导致调用 `std::terminate"?
  • @LorahAttkins 仅当由于另一个异常而在堆栈展开期间完成。不适用于此处。
  • @Deduplicator 这不是标准在其涵盖的许多方面“工作”的方式。例如,取决于启用/禁用的异常处理和优化级别,您可以找到许多标准要求可能与您的特定代码部分无关的场景。对于这方面的进一步讨论,几乎不可能输出合理的编译器诊断问题,为什么突然需要特定代码示例的析构函数的公共可用性。
  • @Secundi 我不确定你指的是我的哪个 cmets。关于第一个:在创建返回值后,这个特定的函数不可能失败,这是它唯一做的事情。因此,dtor 将永远不会被调用。为什么它仍然是按定义可能调用是一个有趣的问题。关于第二个:那应该足够直截了当。顺便说一句,禁用的异常意味着非标准,也显然意味着我的第二条评论中的触发器不能适用。
【解决方案2】:

有很多简单的情况,比如问题中可以很容易地证明析构函数永远不会被使用,但是代码被使用了。

但是,决定这个问题可能会变得任意复杂,这是标准化的祸根。将其留给实施者将因此分裂语言,创建不兼容的子方言,因为他们要付出不同的努力来决定(不同的)极端情况。

但这还不是结束,因为解决问题意味着解决停机问题,因此甚至不是棘手的,而是无法确定的

因此,像CWG 2426 所做的那样回避它不仅是为了理智(指定所有细节会非常快速地变得笨拙),而是在口授任意数量的任意选择的简单案例之后不反复划线的唯一选择。

【讨论】:

  • +1 提到停机问题!我认为就委员会的形式化而言,这里的核心问题是:如果可以想象析构函数的公共可用性是必需的,怀疑取决于优化级别和启用/禁用的异常处理,类设计本身将取决于这些方面,更不用说令人怀疑的可怕的编译器诊断了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-25
相关资源
最近更新 更多