【问题标题】:Do move constructors need attribute that are moveable?移动构造函数是否需要可移动的属性?
【发布时间】:2017-03-16 04:37:06
【问题描述】:

我正在尝试围绕移动构造函数展开思考,并希望通过这个问题获得更多见解。这是一个简单的类。

class A
{
 private:
   vector<B> Bs;
 public:
   /*
   ..
   */

   A(A&& other)
   : Bs
   {
      Bs = other.Bs;
   }
}

即使B 没有移动构造函数,我的移动构造函数看起来是否正确?即使我没有为B 类的对象显式编写移动赋值,移动构造函数是否有效?如果不是,是否意味着如果要移动任何对象,首先必须确保每个属性也是可移动的?

【问题讨论】:

标签: c++ object constructor move move-semantics


【解决方案1】:

如果您的对象包含可移动的对象(例如std::vector),则默认移动构造函数将负责移动,因此您无需执行任何操作。尝试使用Rule of Zero

在您的情况下,不,移动 ctor 不会做正确的事情。它会复制,因为在

Bs = other.Bs;

other.Bs 是函数内部的左值,因为它有一个名称(是的,它指的是右值引用,但other 本身就是一个左值)。你需要

Bs = std::move(other.Bs);

或更好

A(A&& other) : Bs(std::move(other.Bs)) {}

但同样,在这种情况下,您根本不应该编写任何用户定义的移动构造函数。

强烈推荐阅读Howard Hinnant,这可能是对移动语义概念贡献最大的人:http://www.slideshare.net/ripplelabs/howard-hinnant-accu2014

【讨论】:

  • @LogicStuff 更好A(A&amp;&amp; other) = default;
  • 好的,这很有帮助。但是,即使 B 类没有移动构造函数(B 的属性也没有),它真的会起作用吗?只要A 的唯一属性是向量(因此具有移动构造函数),那么我们就不必关心向量包含什么?
  • @NathanOliver 更好的是:即,什么都没有:) 移动 ctor 没有被隐式禁用。
  • @Remi.b 如果成员有一个已删除的移动 ctor,那么您的对象移动 ctor 将被隐式删除。如果成员有一个移动 ctor,那么您的对象的默认移动 ctor 将负责处理业务。
  • 应该是Bs(std::move(other.Bs)) 而不是Bs(std::move(other))
【解决方案2】:

不,不是。您的线路在这里:

Bs = other.Bs;

您正在执行复制作业。在移动构造函数主体中不会更改表达式值类型。左值仍然是左值,右值仍然是右值。

要进行移动分配,它看起来像这样:

Bs = std::move(other.Bs);

但它仍然没有达到应有的效率。

还有一个问题。您的代码未编译,因为您已提交 std::vector 构造函数的大括号。实际上,这是您应该移动值的地方。这是一个例子:

// a kitten dies when your move constructor is not noexcept
A(A&& other) noexcept
  // in the move constructor of A, we move construct it's member too.
: Bs{std::move(other.Bs)}
// empty body
{}

最好的解决方案是:

你是对的,它是空的。如果你不放任何构造函数,编译器会帮你做的。

如果你想添加其他构造函数但让编译器添加它自己的移动构造函数,你可以显式默认它:

// bonus: noexcept when it can.
A(A&&) = default;

最后一件事。如果您的类包含另一个不可移动但可复制的类,它将尽其所能:复制构造它。

让类型C 是一个不可移动但可复制的类。

这是一个例子:

// the `C&&` from the move will bind to the `const C&` of the operator=
auto anotherC = std::move(aC);

【讨论】:

  • 这对 +1 很有帮助。 (1) 没有理由不添加A(A&amp;&amp;) = default;,以防万一不想为其显式编写移动构造函数的任何类,对吧? (2) 如果我理解正确,人们会期望 A(A&amp;&amp;) = default; 与您在答案中编写的“手工”移动构造函数完全相同(尽管它可能取决于编译器)
  • @Remi.b 默认移动构造函数的好处是,如果将来您决定向您的类添加另一个可移动成员,则无需继续修改移动 ctor。
  • 当你定义一个拷贝构造函数时,它会阻止编译器实现它自己的移动构造函数。在这种情况下,A(A&amp;&amp;) = default; 非常有用。否则,编译器会默认添加默认的。
  • @Remi.b 对于您的 (2),不,它会略有不同。即使您滚动自己的移动构造函数,它与编译生成的构造函数完全相同,它也会使您的类不平凡地移动构造。如果你的类包含一堆可移动的变量,编译器生成的会更好,因为它知道这是一个普通的构造函数。
【解决方案3】:

此处的移动构造函数将是“有效的”,因为它将在从 A 类型的右值构造 A 对象时调用。它可以正常工作,创建另一个对象的有效副本。然而,它并没有它应该的那么有效,因为它复制了内部的std::vector 对象。这是性能问题,而不是正确性问题。

这里实际上存在两个性能问题。第一个是构造函数默认构造B子对象,然后分配给它。那很浪费。要解决这个问题:

A::A(A&& other) : B(other.B) {
}

第二个是允许移动构造函数从另一个对象中窃取。为此,实现应该从B 子对象移动:

A::A(A&& other) : B(std::move(other.B)) {
}

最后,回答标题中的问题,std::move 适用于不可移动的类型。它所做的只是将左值转换为右值;不可移动的类型(通常)可以从右值复制,所以除非你正在处理一个非常反常的类型,否则只需在对象中的几乎所有东西上使用std::move。 (移动一个 int 值可能看起来很奇怪,但它是无害的)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-08
    • 2015-08-04
    • 2019-04-09
    • 2012-03-04
    • 1970-01-01
    相关资源
    最近更新 更多