请考虑以下代码:
#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。看看当我们提供定义时会发生什么。