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

Vit*_*meo 90 c++ constructor default move-semantics c++11

这是

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; } 
}
Run Code Online (Sandbox Code Playgroud)

相当于此

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;
}
Run Code Online (Sandbox Code Playgroud)

Pie*_*aud 46

是的,两者都是一样的.

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;
}
Run Code Online (Sandbox Code Playgroud)

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

但是,在声明时必须遵循一些规则explicitly-defaulted-functions:

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

表单的函数定义:

  attribute-speci?er-seqopt decl-speci?er-seqopt declarator virt-speci?er-seqopt = default ;
Run Code Online (Sandbox Code Playgroud)

被称为明确默认的定义.明确默认的函数应该

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

  • 具有相同的声明函数类型(可能不同的ref限定符除外,在复制构造函数或复制赋值运算符的情况下,参数类型可能是"引用非const T",其中T是成员函数的名称)如同隐含声明的那样,

  • 没有默认参数.

  • 您引用的 8.4.2 是什么文档?C++11 标准或 N3690 均不包含 8.4.2/1 中的文本“并且没有*异常规范*”。它们都在 8.4.2/2 中说:“只有当显式默认函数被隐式声明为 `constexpr` 时,才可以将其声明为 `constexpr`,并且只有当它是与隐式声明上的*异常规范*兼容(15.4)。” (2认同)
  • @Casey 好球!我在引用 N3242 ......我混淆了我的文档......我更新了我的帖子以引用 N3690 !感谢您指出这一点! (2认同)
  • 如果我将移动构造函数和赋值运算符设置为 `= default`,我是否能够与对象交换?我不需要将构造函数声明为`noexcept`吗?我尝试将 `noexcept` 和 `=default` 都放在一起,但这不会编译。 (2认同)

Sha*_*our 19

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

Example(Example&& mE) : a{move(mE.a)}, b{move(mE.b)} { }
Run Code Online (Sandbox Code Playgroud)

相当于:

Example(Example&& mE)                 = default;
Run Code Online (Sandbox Code Playgroud)

我们可以通过转到草案C++ 11标准部分12.8 复制和移动类对象13段来看到这一点(强调我的未来):

被默认并为已删除不限定的复制/移动的构造 隐含定义如果odrused(3.2)或当它在其第一声明之后显式默认值.[注意:即使实现省略了odr-use(3.2,12.2),也会隐式定义复制/移动构造函数. - 注意事项] [...]

和第15段说:

非联合类X 的隐式定义的复制/移动构造函数执行其基础和成员的成员复制/移动.[注意:忽略非静态数据成员的大括号或大小写.另请参见12.6.2中的示例.-end note]初始化顺序与用户定义构造函数中基数和成员的初始化顺序相同(见12.6.2).设x是构造函数的参数,或者对于移动构造函数,x是引用参数的xvalue.以适合其类型的方式复制/移动每个基本或非静态数据成员:

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

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


ant*_*_rh 8

是一个=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
}
Run Code Online (Sandbox Code Playgroud)

它打印:

copy
Run Code Online (Sandbox Code Playgroud)

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

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

但是,如果non_movable根本不声明的move构造函数,则将将move构造函数用于movable(以及每个具有move构造函数的成员),将将copy构造函数用于nonmovable(以及每个未定义move构造函数的成员) )。参见示例:

#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));
}
Run Code Online (Sandbox Code Playgroud)

它打印:

movable::move
nonmovable::copy
Run Code Online (Sandbox Code Playgroud)

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

更新:但是,如果您注释掉该行has_nonmovable(has_nonmovable &&) = default;,则两个成员都将使用副本:http : //coliru.stacked-crooked.com/a/171fd0ce335327cd-打印:

movable::copy
nonmovable::copy
Run Code Online (Sandbox Code Playgroud)

因此,将其放到=default任何地方仍然很有意义。这并不意味着您的移动表达式将始终移动,但是这样做的机会更高。

再进行一次更新:但是如果将has_nonmovable(const has_nonmovable &) = default;其中任一行注释掉,那么结果将是:

movable::move
nonmovable::copy
Run Code Online (Sandbox Code Playgroud)

因此,如果您想知道程序中会发生什么,只需自己做所有事情即可:

  • 这就是*隐式声明*和*显式删除*之间的区别。当您显式删除一个特殊成员时,它保留在重载集中,并且如果选择该程序,则该程序的格式将不正确。当隐式删除它时,将从过载集中删除它。请参见[删除隐式声明的move构造函数](https://en.cppreference.com/w/cpp/language/move_constructor) (2认同)
  • @Caleth,但我愿意。对我来说,最好是编译器给我消息:`不能显式声明默认的move构造函数,因为它将格式错误。没有这个,我只是认为我的** move **表达式实际上根本不是一个移动表达式,因为移动构造函数被编译器隐式删除了。显式表达使隐式事物成为现实。这非常令人困惑。 (2认同)