是否允许复制/移动省略使使用已删除函数的程序格式正确?

Is copy/move elision allowed to make a program using deleted functions well-formed?

本文关键字:删除 函数 程序格式 许复制 复制 移动省 是否      更新时间:2023-10-16

请考虑以下代码:

#include <iostream>
struct Thing
{
Thing(void)                       {std::cout << __PRETTY_FUNCTION__ << std::endl;}
Thing(Thing const &)              = delete;
Thing(Thing &&)                   = delete;
Thing & operator =(Thing const &) = delete;
Thing & operator =(Thing &&)      = delete;
};
int main()
{
Thing thing{Thing{}};
}

我希望Thing thing{Thing{}};语句意味着使用默认构造函数构造Thing类的临时对象thing并使用移动构造函数构造Thing类的对象,并刚刚创建的临时对象作为参数。我希望这个程序被认为是格式错误的,因为它包含对已删除移动构造函数的调用,即使它可能被省略。标准的class.copy.elision部分似乎也要求这样做:

即使省略了调用,所选构造函数也必须可访问

通过简化的价值类别保证副本省略的措辞似乎也不允许这样做。

然而,gcc 7.2(以及clang 4,但不是仍然不支持保证复制的VS2017)将编译此代码,只是很好地省略了移动构造函数调用。

在这种情况下,哪种行为是正确的?

它不会生成格式错误的程序。它完全摆脱了对已删除函数的引用。提案中的适当措辞如下:

[dcl.init] 项目符号 17.6

如果初始值设定项表达式是 prvalue 且 cv 不限定 源类型的版本与 目标,初始值设定项表达式用于初始化 目标对象。[ 示例:T x = T(T(T(T()))); 调用 T 默认值 用于初始化 x 的构造函数。

这个例子进一步加强了这一点。因为它指示整个表达式必须折叠成单个默认构造。

需要注意的是,由于值类别而省略副本时,永远不会使用已删除的函数,因此程序不会引用它。

这是一个重要的区别,因为另一种形式的复制省略仍然使用复制 c'tor,如下所述:

[basic.def.odr]/3

。选择用于复制或移动类类型的对象的构造函数是 ODR 使用,即使调用实际上被实现省略了 ([类.副本] ...

[class.copy] 描述了另一种形式的允许(但不是强制性的)复制省略。如果我们与您的班级一起演示:

Thing foo() {
Thing t;
return t; // Can be elided according to [class.copy.elision] still odr-used
}

应该使程序格式不正确。海湾合作委员会如预期的那样抱怨它。


顺便说一下。如果你认为在线编译器中的前面的例子是一个魔术师的把戏,而GCC抱怨,因为它需要调用移动c'tor。看看当我们提供定义时会发生什么。