提问人:Fedor 提问时间:8/5/2021 最后编辑:cigienFedor 更新时间:8/5/2021 访问量:2036
为什么 C++ 中的强制 RVO 需要公共析构函数?
Why is public destructor necessary for mandatory RVO in C++?
问:
请考虑下面的简单示例,其中函数返回一个带有私有析构函数的类对象,并且必须进行强制返回值优化 (RVO):bar
A
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
哪一个编译器就在这里?
答:
48赞
Brian Bi
8/5/2021
#1
这是CWG 2426。析构函数可能会在此上下文中被调用,因为即使在返回对象初始化之后,函数仍有可能无法成功完成:在语句期间创建的任何临时变量以及作用域内的自动局部变量都必须被销毁,如果销毁引发,则作为堆栈展开的一部分,对象将被销毁。编译器应要求析构函数此时可访问。A
return
A
注 1:函数最外层局部变量的析构函数抛出的异常可以被函数 try 块捕获。
注2:返回对象销毁后,允许处理程序执行另一条语句。标准中有一个这样的例子。return
评论
3赞
Deduplicator
8/5/2021
在这个特定的上下文(OPs 示例)中,可以很容易地证明析构函数从未被调用过。其他例子就不那么好了。
0赞
Lorah Attkins
8/5/2021
关于“注1”:析构函数抛出的异常不会导致调用“std::terminate”吗?
7赞
Deduplicator
8/5/2021
@LorahAttkins 仅当在堆叠放卷期间由于其他异常而完成时。此处不适用。
0赞
Secundi
9/22/2021
@Deduplicator 这不是标准所涵盖的许多方面的“工作”方式。例如,根据启用/禁用的异常处理和优化级别,您可以找到许多标准要求可能与特定代码部分无关的场景。对于这方面,几乎不可能输出合理的编译器诊断,因为有疑问,为什么突然需要析构函数的公共可用性来满足特定代码示例的需求。
0赞
Deduplicator
9/22/2021
@Secundi我不确定你的意思是哪条评论。关于第一个:创建返回值后,这个特定函数不会失败,这是它唯一做的事情。因此,永远不会调用 dtor。为什么它仍然可能被按定义调用是一个有趣的问题。关于第二个:这应该足够直截了当。顺便说一句,禁用的例外意味着非标准,也显然意味着我第二条评论中的触发器不适用。
15赞
Deduplicator
8/5/2021
#2
有许多简单的情况,例如在问题中,可以很容易地证明析构函数永远不会被使用,但代码被使用。
但是,决定这个问题可能会变得非常复杂,这是标准化的祸根。因此,将它交给实施者会分裂语言,在他们做出(不同的)极端情况时付出不同的努力来创造不兼容的子方言。
但即便如此,这还不是结束,因为解决问题意味着解决停滞不前的问题,因此甚至不是棘手的,而是无法决定的。
因此,像CWG 2426那样回避它不仅是为了理智(指定所有细节变得非常笨拙),而且是唯一的选择,而不是在口述任意选择的简单案例后反复无常地划清界限。
评论
0赞
Secundi
9/22/2021
+1 提到停止问题!我认为就委员会的形式化而言,这里的核心问题是:如果可以想象析构函数的公开可用性是必需的,那么根据优化级别和启用/禁用异常处理,类设计本身将取决于这些方面,更不用说可怕的编译器诊断了。
0赞
Artyer
4/8/2022
事实并非如此。在CWG2426之前,CWG2176添加的措辞将决定是否可以调用析构函数,并且不需要额外的措辞。只有当返回后销毁的任何内容(return 语句中的临时变量 + 自动变量)具有带有析构函数的类型时,编译器在编译 return 语句后需要调用的析构函数时,编译器无论如何都必须枚举该类型noexcept(false)
评论