为什么我需要在move-constructor的初始化列表中使用std :: move?

时间:2012-10-30 12:20:46

标签: c++ c++11 rvalue-reference move-semantics move-constructor

假设我有一个(普通的)类,它是可移动构造和可移动分配但不可复制构造或可复制分配:

class movable
{
  public:
    explicit movable(int) {}
    movable(movable&&) {}
    movable& operator=(movable&&) { return *this; }
    movable(const movable&) = delete;
    movable& operator=(const movable&) = delete;
};

这很好用:

movable m1(movable(17));

当然,这不起作用,因为m1不是右值:

movable m2(m1);

但是,我可以将m1包裹在std::move中,并将其转换为右值引用,以使其有效:

movable m2(std::move(m1));

到目前为止,这么好。现在,假设我有一个(同样微不足道的)容器类,它包含一个值:

template <typename T>
class container
{
  public:
    explicit container(T&& value) : value_(value) {}
  private:
    T value_;
};

然而,这不起作用:

container<movable> c(movable(17));

编译器(我尝试过clang 4.0和g ++ 4.7.2)抱怨我试图在movable的初始化列表中使用container删除的拷贝构造函数。同样,将value包裹在std::move中可以使其正常工作:

    explicit container(T&& value) : value_(std::move(value)) {}

但是为什么在这种情况下需要std::movevalue类型movable&&已经不是value_(value)了吗? movable m1(movable(42))与{{1}}的区别如何?

2 个答案:

答案 0 :(得分:16)

那是因为value是一个命名变量,因此是一个左值。 std::move需要将其强制转换为右值,这样才会导致T的move-constructor重载匹配。

换句话说:右值引用可以绑定到右值,但它本身不是右值。它只是一个引用,在表达式中它是一个左值。从中创建一个rvalue表达式的唯一方法是通过强制转换。

答案 1 :(得分:5)

  

value_(value)movable m1(movable(42))的区别如何?

命名的右值引用是左值(因此将绑定到已删除的副本ctor),而临时值则是右值(prvalue是特定的)。

§5 [expr] p6

  

[...]通常,此规则的作用是将命名的右值引用视为左值,对对象的未命名右值引用被视为xvalues [...]

以及示例:

A&& ar = static_cast<A&&>(a);
  

表达式ar是左值。

以上引用来自非规范性说明,但是是一个充分的解释,因为第5条的其余部分解释了哪些表达式创建xvalues (又名,只有指定的表达式,否则将创建xvalues)。有关详尽列表,另请参阅here

†xvalues是rvalues的一个子组,prvalues是另一个子组。有关说明,请参阅this question