为什么需要使用std::move来调用std::vector的移动赋值运算符?

7
我正在学习C++11,关于移动语义和右值引用,我有一个问题。我的示例代码如下(C++ Shell URL为 cpp.sh/8gt):
#include <iostream>
#include <vector>

void aaa(std::vector<int>&& a)
{
    std::cout << "size of a before move: " << a.size() << std::endl;
    std::vector<int> v;
    v = a; /*std::move(a)*/
    std::cout << "size of a after move: " << a.size() << std::endl;
}

int main ()
{
  std::vector<int> foo (3,0);

  aaa(std::move(foo));

  return 0;
}

这是结果:
size of a before move: 3  
size of a after move: 3
在函数aaa的第v = a行,似乎std :: vector的移动分配运算符没有被调用,否则a将具有大小0而不是3。
但是,如果我将v = a更改为v = std :: move(a),输出将变为

移动前a的大小:3
移动后a的大小:0

我认为这次已经调用了std :: vector的移动分配运算符。

我的问题是为什么第一次未调用分配运算符? 根据c ++参考,std :: vector具有接受右值引用的分配运算符。

复制(1)vector& operator =(const vector& x);
移动(2)vector& operator =(vector&& x );
初始值设定项列表(3)vector& operator =(initializer_list il);

然后在v = a行,由于a声明为右值引用,应当调用移动运算符。 为什么我们仍然需要使用std :: move包装a?

非常感谢!

[编辑] 我认为Loopunroller和Kerrek SB都回答了我的问题。 谢谢! 我不认为我可以选择两个答案,所以我会选择第一个。


2
对于右值引用 variable 的表达式是左值,因为你可以多次引用该变量。 - dyp
2
因为“rvalue引用”是一个糟糕且令人困惑的名称。而且C++也是糟糕且令人困惑的。 - Lightness Races in Orbit
2
当然,这是一件好事(以一种不好和令人困惑的方式)。否则,v = a 将会默默地修改 a,导致正是移动语义旨在避免的 auto_ptr 风格的怪异行为。 - Mike Seymour
2个回答

11

这篇来自[expr]/6的说明可能会阐明正在发生的事情(强调是我的):

[注意:如果表达式满足以下条件,则它是xvalue:

  • 调用返回对象类型的rvalue引用的函数的结果,无论是隐式的还是显式的,
  • 将其转换为对象类型的rvalue引用,
  • 用于表示具有非引用类型的非静态数据成员的类成员访问表达式中的对象表达式为xvalue,或者
  • 将第一操作数作为xvalue且第二操作数作为数据成员指针的.*成员指针表达式。

一般来说,这个规则的效果是,命名的rvalue引用被视为lvalue,而对象的未命名rvalue引用被视为xvalue; 不管是否命名,对函数的rvalue引用都被视为lvalue。 - 结束注释]

可以轻松地看出,根据以上列表,表达式std::move(a)是一个xvalue (第一条)。 a是一个rvalue引用的名称,因此作为表达式是一个lvalue。


9

a这个表达式是一个左值。而std::move(a)这个表达式是一个右值。只有右值可以绑定到右值引用上,这构成了移动构造函数和移动赋值运算符。

值得重复强调的是:评估任何引用变量,以及解引用任何可解引用指针,都会产生一个左值。


3
重要的一点是为什么这条规则会存在。当他调用a = v时,编译器无法知道他是否还需要使用v(事实上,他确实在稍后使用了它),因此除非您明确授权,否则它不能冒险更改该值。 - James Kanze
@JamesKanze:没错。作为黄金法则,rvalue 应该保证不会被别名引用。虽然这在语言级别上并没有得到强制执行(其他像 Rust 的语言确实实施了这一点),但这应该是人们思考 rvalue 的方式。 - Kerrek SB
另一种记忆这个概念的方法是,右值引用 是在谈论可以被 绑定 到引用上的内容:一个右值。这并不意味着使用引用和使用右值是相同的。 - M.M
@KerrekSB 基本思想是rvalue引用绑定到一个“即将被丢弃”的对象;一个不会再次使用的对象。如果对象是临时的(rvalue),或从函数返回,它自动成为可丢弃的对象,因为没有办法再次使用它。否则,您必须告诉编译器您不再使用该对象。使用 std::move 的方法可以达到这个目的。 - James Kanze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接