为什么在这里涉及move构造函数

时间:2018-06-27 10:05:40

标签: c++ c++11 initialization copy-elision

我有这段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的构造似乎涉及该类的move构造函数:

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

似乎编译器要创建临时对象,然后将其移动到c1c2。为什么会这样呢?这三个语句不应该只调用MyClass(Args& a)构造函数吗?

另一方面,如果我创建了move构造函数,则程序可以正常编译,并且永远不会调用move构造函数!!!

3 个答案:

答案 0 :(得分:9)

请参见copy elision

  

在以下情况下,允许编译器使用,但不需要编译器省略类对象的copy-和move-(自C ++ 11起)构造,即使复制/移动(自C ++ 11起)构造函数和析构函数具有明显的副作用。这是一种优化:即使发生并且未调用copy- / move-constructor,它也必须存在并且可以访问(好像根本没有优化发生),否则程序格式错误。

自C ++ 17起:

  

它们不必存在或不可访问,因为语言规则可确保即使在概念上也不会进行复制/移动操作。

答案 1 :(得分:6)

  

为什么会这样?这三个语句不应该只调用MyClass(Args& a)构造函数吗?

对于MyClass c1 = MyClass(a);MyClass c2 = a;,临时MyClass将首先由构造函数MyClass::MyClass(Args& a)构造,然后用于复制初始化对象c1c2。构造的临时变量是右值,这意味着将为复制初始化选择move-constructor。

  

另一方面,如果我创建了move构造函数,则程序可以正常编译,并且永远不会调用move构造函数!!!

原因是copy elision;复制/移动操作在此处被省略,导致使用MyClass::MyClass(Args& a)直接初始化对象c1c2的事实。

自C ++ 17起,有关复制删除的规则已更改。请注意,在C ++ 17之前的版本中,不能保证复制省略。对于非保证的复制省略,即使省略了复制/移动操作,复制/移动构造函数仍然需要存在并且可以访问。

  

这是一种优化:即使发生并且未调用copy- / move-constructor,它也必须存在并且可以访问(好像根本没有优化发生),否则程序格式错误。

在C ++ 17之后,您的代码可以正常工作。为了保证复制省略,不需要复制/移动构造函数存在或可访问。

  

在以下情况下,要求编译器省略   类对象的复制和移动构造,即使   复制/移动构造函数和析构函数具有明显的副作用。   由于语言规则可确保它们不存在或不可访问   即使在概念上也没有进行复制/移动操作:

     
      
  • 在初始化中,如果初始值设定项表达式是prvalue,并且源类型的cv不合格版本与   目标的类,初始化器表达式用于   初始化目标对象:

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

答案 2 :(得分:6)

一个主要问题是:

MyClass c1 = MyClass(a);

这将创建类型为MyClass临时对象,临时类型为右值。然后,它尝试使用临时对象 copy-construct c1,或者如果您有移动构造器,则尝试 move-construct c1

在C ++ 11和C ++ 14中,不会为您的类自动生成复制构造函数(因为您有一个用户定义(即使已删除)的move-constructor),因此只有已删除的move -constructor可用。好吧,如果不删除它,它将可用,从而导致您的错误。