【问题标题】:Is a `=default` move constructor equivalent to a member-wise move constructor?`=default` 移动构造函数是否等同于成员移动构造函数?
【发布时间】:2014-11-08 23:44:24
【问题描述】:

这是

struct Example { 
    string a, b; 

    Example(Example&& mE) : a{move(mE.a)}, b{move(mE.b)} { }
    Example& operator=(Example&& mE) { a = move(mE.a); b = move(mE.b); return *this; } 
}

相当于这个

struct Example { 
    string a, b;

    Example(Example&& mE)            = default;
    Example& operator=(Example&& mE) = default;
}

?

【问题讨论】:

  • @DieterLücking:显然不是,尽管它的主题相似,而且一些答案可能涵盖相似的领域。但是,我们不会将关于移动语义的每一个问题都视为彼此重复。
  • 注意,我在这个问题上添加了我的答案,因为当时我正在寻找标准的报价,证明它们是等效的,而公认的答案并没有做到这一点。所以,我刚刚找到了引用并添加了我的答案。
  • 我还想提一下,在您的示例中,默认构造函数 未声明析构函数 默认 - 见Howard Hinnant - compiler implicit declares

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


【解决方案1】:

是的,两者都是一样的。

但是

struct Example { 
    string a, b; 

    Example(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 是成员函数类的名称)就好像它已被隐式声明一样,

  • 没有默认参数。

【讨论】:

  • 您引用的 8.4.2 来自哪个文件? C++11 标准或 N3690 在 8.4.2/1 中均不包含文本“,并且没有 异常规范”。他们在 8.4.2/2 中都说:“一个显式默认的函数只有在隐式声明为 constexpr 时才可以声明为 constexpr,并且可能有一个显式的异常规范 仅当它与隐式声明中的 异常规范 兼容 (15.4) 时。"
  • @Casey 好收获!我在引用 N3242...我混淆了我的文档...我更新了我的帖子以引用 N3690!感谢您指出这一点!
  • 如果我将移动构造函数和赋值运算符设置为= default,我可以与对象交换吗?我不需要将构造函数声明为noexcept 吗?我尝试将noexcept=default 都放在两者上,但这不会编译。
  • 声明复制构造函数或赋值或析构函数会阻止默认移动构造函数的生成。如果我们定义了这些函数中的任何一个,我们必须定义移动构造函数。但是如果定义了case复制构造函数但是默认关键字创建了move构造函数呢?它是否按预期工作?
【解决方案2】:

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

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) 或显式定义 在第一次声明后默认。 [注:复制/移动 即使省略了实现,构造函数也是隐式定义的 其 odr 使用 (3.2, 12.2)。 ——尾注][...]

和第15段说:

非联合类 X 的隐式定义的复制/移动构造函数 对其基地和成员执行成员复制/移动。 [ 笔记: 非静态数据成员的大括号或相等初始化器将被忽略。 另见 12.6.2 中的示例。 ——尾注] 的顺序 初始化与基的初始化顺序相同, 用户定义的构造函数中的成员(见 12.6.2)。让 x 要么 构造函数的参数,或者对于移动构造函数,一个 xvalue 指的是参数。每个基本或非静态数据成员 以适合其类型的方式复制/移动:

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

虚拟基类子对象只能由 隐式定义的复制/移动构造函数(参见 12.6.2)。

【讨论】:

    【解决方案3】:

    =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 &amp;&amp;) = default 显式声明了移动构造函数) .

    但是如果根本没有声明non_movable 的移动构造函数,则移动构造函数将用于movable(以及每个具有移动构造函数的成员),而复制构造函数将用于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 &amp;&amp;) = default; 行,那么两个成员都将使用副本:http://coliru.stacked-crooked.com/a/171fd0ce335327cd - 打印:

    movable::copy
    nonmovable::copy
    

    所以可能将=default 放在任何地方仍然是有意义的。这并不意味着你的移动表达式会一直移动,但它会提高这种可能性。

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

    movable::move
    nonmovable::copy
    

    因此,如果您想知道程序中发生了什么,请自己做所有事情:sigh:

    【讨论】:

    • 在您的第一个示例中,显式默认的移动构造函数是 deleted,因为这就是隐式移动构造函数。当出现右值时,重载分辨率可以消除复制和移动之间的歧义
    • has_nonmovable(has_nonmovable&amp;&amp;)= defaultresult 是一个已删除的移动构造函数,因为成员中有 = delete 的移动构造函数。您要求一个与默认生成的内容相匹配的移动构造函数,然后得到它。
    • 这就是隐式声明删除显式删除的区别。当您显式删除一个特殊成员时,它仍保留在重载集中,如果选择它,程序就是非良构的。当它被隐式删除时,它会从重载集中删除。见Deleted implicitly-declared move constructor
    • 当且仅当成员明智的举动是不正确的,所以我不认为它是不同的
    • @Caleth,但我愿意。对我来说,如果编译器只给我以下信息会更好:cannot declare explicitly defaulted move constructor because it would be ill-formed。如果没有这个,我只是认为我的 move 表达式在它实际上根本不是移动表达式时移动,因为移动构造函数被编译器隐式删除。显式表达使事物隐含。这非常令人困惑。
    【解决方案4】:

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

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

    【讨论】:

    • 但是代码不能改变子对象构造的顺序。该语言忽略构造函数的成员初始化器列表的顺序,并始终以一致的顺序构造(和析构)类子对象。所以改变它不会导致构造函数不等价。
    猜你喜欢
    • 1970-01-01
    • 2013-03-27
    • 2011-05-22
    • 1970-01-01
    • 1970-01-01
    • 2011-09-13
    • 1970-01-01
    • 2013-01-08
    相关资源
    最近更新 更多