为什么这里会涉及移动构造函数?

8

我有这段C++代码:

class Args {};

class MyClass {
  public:
  MyClass(Args& a) {}
  MyClass(MyClass &&) = delete;
};

int main() {

  Args a;
  MyClass c1 = MyClass(a);
  MyClass c2 = a;
  MyClass c3(a);

  return 0;
}

这段代码无法编译,因为创建对象 c1c2 似乎涉及类的移动构造函数:

error: use of deleted function ‘MyClass::MyClass(MyClass&&)’

似乎编译器想要创建临时对象,然后将它们移动到 c1c2。这是怎么回事?难道这三个语句不应该只调用 MyClass(Args& a) 构造函数吗?

另一方面,如果我确实创建了移动构造函数,则程序可以成功编译,并且从未调用移动构造函数!!!


2
在C++17中,你的代码应该没问题了,因为复制省略已经生效。 - Jarod42
3个回答

10
请参见复制省略:
在以下情况下,编译器可以但不要求省略类对象的复制和移动 (自 C++11 起) 构造,即使复制/移动(自 C++11 起)构造函数和析构函数具有可观察的副作用。这是一种优化:即使发生了此优化且未调用复制/移动构造函数,它仍然必须存在并且可访问(就好像没有进行任何优化一样),否则程序将是非法的。
自 C++17 起:
它们不需要存在或可访问,因为语言规则确保不会发生任何复制/移动操作,即使是概念上的。

6

一个主要问题是这样的:

MyClass c1 = MyClass(a);

这段代码创建了一个MyClass类型的临时对象,而临时对象是rvalue。然后它尝试使用这个临时对象复制构造c1,或者如果你有一个移动构造函数,那么c1将被移动构造
在C++11和C++14中,如果你有一个用户定义的(即使是删除的)移动构造函数,那么你的类将不会有自动生成的复制构造函数,因此只有删除的移动构造函数可用。如果移动构造函数没有被删除,那么就会导致错误。

6
为什么会出现这种情况?这三个语句不都只是调用了MyClass(Args& a)构造函数吗?
对于MyClass c1 = MyClass(a);MyClass c2 = a;,在第一次调用构造函数MyClass::MyClass(Args& a)时,将首先构建临时的MyClass对象,然后用于复制初始化c1c2。构建的临时对象是右值,这意味着将为复制初始化选择移动构造函数。
另一方面,如果我确实创建了移动构造函数,则程序编译正常,但移动构造函数从未被调用!!!
原因是拷贝省略;拷贝/移动操作在此处被省略,结果是MyClass::MyClass(Args& a)直接用于初始化对象c1c2
关于拷贝省略的规则自C++17以来已经发生了变化。请注意,在C++17之前,不能保证拷贝省略。对于非保证的拷贝省略,即使省略了拷贝/移动操作,拷贝/移动构造函数仍然需要存在且可访问。
这是一种优化:即使进行了优化并且未调用拷贝/移动构造函数,它仍必须存在且可访问(就像没有进行任何优化一样),否则程序将不合法。
在C++17之后,您的代码将正常工作。对于保证的拷贝省略,拷贝/移动构造函数不需要存在或可访问。

Under the following circumstances, the compilers are required to omit the copy- and move- construction of class objects even if the copy/move constructor and the destructor have observable side-effects. They need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually:

  • In initialization, if the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object:

    T x = T(T(T())); // only one call to default constructor of T, to initialize x
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接