是一个`= default`移动构造函数,等同于成员移动构造函数吗?

时间:2013-08-17 15:41:43

标签: c++ c++11 constructor default move-semantics

这是

struct Example { 
    int a, b; 
    Example(int mA, int mB) : a{mA}, b{mB}               { }
    Example(const Example& mE) : a{mE.a}, b{mE.b}        { }
    Example(Example&& mE) : a{move(mE.a)}, b{move(mE.b)} { }
    Example& operator=(const Example& mE) { a = mE.a; b = mE.b; return *this; } 
    Example& operator=(Example&& mE)      { a = move(mE.a); b = move(mE.b); return *this; } 
}

相当于此

struct Example { 
    int a, b; 
    Example(int mA, int mB) : a{mA}, b{mB} { }
    Example(const Example& mE)            = default;
    Example(Example&& mE)                 = default;
    Example& operator=(const Example& mE) = default;
    Example& operator=(Example&& mE)      = default;
}

4 个答案:

答案 0 :(得分:43)

是的,两者都是一样的。

但是

struct Example { 
    int a, b; 
    Example(int mA, int mB) : a{mA}, b{mB} { }
    Example(const Example& mE)            = default;
    Example(Example&& mE)                 = default;
    Example& operator=(const Example& mE) = default;
    Example& operator=(Example&& mE)      = default;
}

此版本允许您跳过正文定义。

但是,在声明explicitly-defaulted-functions

时,您必须遵循一些规则
  

8.4.2明确违约的函数[dcl.fct.def.default]

     

表单的函数定义:

  attribute-specifier-seqopt decl-specifier-seqopt declarator virt-specifier-seqopt = default ;
     

称为显式默认定义。明确默认的函数

     
      
  • 是一个特殊的会员功能,

  •   
  • 具有相同的声明函数类型(可能不同的 ref-qualifiers 除外,在复制构造函数或复制赋值运算符的情况下,参数类型可能是“引用非const T“,其中T是成员函数类的名称),就像它已被隐式声明一样,

  •   
  • 没有默认参数。

  •   

答案 1 :(得分:18)

是的,默认的移动构造函数将执行其基础和成员的成员移动,因此:

Example(Example&& mE) : a{move(mE.a)}, b{move(mE.b)} { }

相当于:

Example(Example&& mE)                 = default;

我们可以通过转到draft C++11 standard部分12.8 复制和移动类对象 13 来看到这一点(强调我的前进):

  

默认且未定义为已删除的复制/移动构造函数   是隐含定义的,如果它是odrused(3.2)或明确定义的话   在第一次宣布后违约。 [注:复制/移动   即使实现被省略,也会隐式定义构造函数   它的使用(3.2,12.2)。 - 尾注] [...]

15 段落说:

  

非联合类X的隐式定义的复制/移动构造函数   执行其基础和成员的成员复制/移动。 [ 注意:   将忽略非静态数据成员的brace-or-equal-initializers。   另请参见12.6.2中的示例。 - 结束注意]的顺序   初始化与base的初始化顺序相同   用户定义的构造函数中的成员(见12.6.2)。设x是其中之一   构造函数的参数,或者,对于移动构造函数,一个   xvalue引用参数。每个基础或非静态数据成员   以适合其类型的方式复制/移动:

     
      
  • 如果成员是一个数组,则使用x的相应子对象直接初始化每个元素;
  •   
  • 如果成员m具有右值参考类型T&&,则使用static_cast(x.m)进行直接初始化;
  •   
  • 否则,使用x的相应基数或成员对基础或成员进行直接初始化。
  •   
     

虚拟基类子对象只能被初始化一次   隐式定义的复制/移动构造函数(见12.6.2)。

答案 2 :(得分:7)

  

=default移动构造函数是否等同于成员移动构造函数?

更新:嗯,并非总是如此。看看这个例子:

#include <iostream>

struct nonmovable
{
    nonmovable() = default;

    nonmovable(const nonmovable  &) = default;
    nonmovable(      nonmovable &&) = delete;
};

struct movable
{
    movable() = default;

    movable(const movable  &) { std::cerr << "copy" << std::endl; }
    movable(      movable &&) { std::cerr << "move" << std::endl; }
};

struct has_nonmovable
{
    movable    a;
    nonmovable b;

    has_nonmovable() = default;

    has_nonmovable(const has_nonmovable  &) = default;
    has_nonmovable(      has_nonmovable &&) = default;
};

int main()
{
    has_nonmovable c;
    has_nonmovable d(std::move(c)); // prints copy
}

打印:

copy

http://coliru.stacked-crooked.com/a/62c0a0aaec15b0eb

您声明了默认的移动构造函数,但是复制发生而不是移动。为什么?因为如果一个类甚至只有一个不可移动的成员,那么 显式默认的移动构造函数是 隐式已删除(这样的双关语)。因此,当您运行has_nonmovable d = std::move(c)时,实际上会调用复制构造函数,因为has_nonmovable的移动构造函数被删除(隐式),它只是不存在(即使您通过表达式显式声明了移动构造函数) has_nonmovable(has_nonmovable &&) = default)。

但是如果non_movable的移动构造函数根本没有被声明,则移动构造函数将用于movable(并且对于具有移动构造函数的每个成员),并且将使用复制构造函数for nonmovable(以及没有定义移动构造函数的每个成员)。参见示例:

#include <iostream>

struct nonmovable
{
    nonmovable() = default;

    nonmovable(const nonmovable  &) { std::cerr << "nonmovable::copy" << std::endl; }
    //nonmovable(      nonmovable &&) = delete;
};

struct movable
{
    movable() = default;

    movable(const movable  &) { std::cerr << "movable::copy" << std::endl; }
    movable(      movable &&) { std::cerr << "movable::move" << std::endl; }
};

struct has_nonmovable
{
    movable    a;
    nonmovable b;

    has_nonmovable() = default;

    has_nonmovable(const has_nonmovable  &) = default;
    has_nonmovable(      has_nonmovable &&) = default;
};

int main()
{
    has_nonmovable c;
    has_nonmovable d(std::move(c));
}

打印:

movable::move
nonmovable::copy

http://coliru.stacked-crooked.com/a/420cc6c80ddac407

更新但如果你注释掉has_nonmovable(has_nonmovable &&) = default;行,那么副本将用于两个成员:http://coliru.stacked-crooked.com/a/171fd0ce335327cd - 打印:

movable::copy
nonmovable::copy

因此,将=default放在任何地方仍然有意义。这并不意味着你的移动表达式总是会移动,但它会让这种移动表达更高。

还有一次更新:但是如果注释掉has_nonmovable(const has_nonmovable &) = default;行,那么结果将是:

movable::move
nonmovable::copy

因此,如果您想知道您的计划中发生了什么,请自己做所有事情:感叹:

答案 3 :(得分:-1)

除了非常病态的病例......是的。

更准确地说,您还必须考虑具有完全相同规则的最终基础Example。首先是基数-in声明顺序 - 然后是成员,始终按声明顺序。