Fork me on GitHub

对象移动

对象移动

新标准的一个最主要的特性是可以移动而非拷贝对象的能力。很多情况下都会发生对象拷贝。在其中某些情况下,对象拷贝后就立即被销毁了。在这些情况下,移动而非拷贝对象会大幅度提升性能。

noet: 标准库容器、string和shared_ptr类及支持移动也支持拷贝。IO类和unique_ptr类可以移动但不能拷贝。

右值引用

为了支持移动操作,新标准引入了一种新的引用类型——右值引用,就是必须绑定到右值的引用。我们通过&&而不是&来获得右值引用。右值引用一个重要的特性就是只能绑定到将要销毁的对象。

左值和右值是表达式的属性,一些表达式生成或要求左值,而另一些则生成或要求右值。一般而言,一个左值表达式表示的是一个对象的身份,而右值表达式表示的是对象的值。可以取地址的、有名字的就是左值;不能取地址的、没有名字的就是右值。)两者明显的区别就是左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的临时对象

类似于常规引用(左值引用),一个右值引用也不过是某个对象的另一个名字而已。我们不能将左值引用绑定到要求转换的表达式、字面常量或是返回值的表达式,也不能把右值应用直接绑定到一个左值上。但是,常量左值引用可以绑定到非常量左值、常量左值、右值,是一个万能引用类型。不过相比右值引用所引用的右值,常量左值引用所引用的右值在它的“余生”中只能是只读的。

1
2
3
4
5
6
int i = 42;
int &r = i; //r引用i
int &r2 = i*2; //错误,i*2是一个右值
int &&rr = i; //错误,不能将一个右值引用绑定到一个左值上
int &&rr2 = i*2; //正确,将rr2绑定到一个乘法结果上
const int &r3 = i*2; //正确,将一个常量引用绑定到一个右值上

左值持久;右值短暂

考虑左值和右值表达式的列表,两者相互区别之处就很明显了:左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。

由于右值引用只能绑定到临时对象,我们得知

  • 所引用的对象将要被销毁
  • 该对象没有其他用户

这两个特性意味着:使用右值引用的代码可以自由地接管所引用的对象的资源。

注意: 右值引用指向将要被销毁的对象。因此,我们可以从绑定到右值引用的对象“窃取”状态

变量时左值

1
2
int &&rr1 = 42; //正确:字面值常量是右值
int &&rr2 = rr1; //错误:表达式rr1是左值

变量是左值,因此我们不能将一个右值引用直接绑定到一个变量上,即使这个变量是右值引用类型也不行。

标准库move函数

我们可以显示地将一个左值转换为对应的右值引用类型。我们还可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用,此函数定义在头文件utility中。

1
int &&rr3 = std::move(rr1); //ok

note: 我们可以销毁一个移后源对象,也可以赋予它新值,但不能使用一个移后源对象的值。

warning: 使用move的代码应该使用std::move而不是move。这样做可以避免潜在的名字冲突。

移动构造函数和移动赋值运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <string>
using namespace std;
struct X{
string i; //内置类型可以移动
string s; //string定义了自己的移动操作
};
int main()
{
X x;
x.i = "cqu";
x.s = "wangxinri";
cout<<x.i<<" "<<x.s<<endl; //cqu wangxinri
//使用合成的移动构造函数
X x2 = std::move(x); //移后源对象x必须保持有效的、可析构的状态
cout<<x2.i<<" "<<x2.s<<endl; //cqu wangxinri
cout<<"---------"<<endl;
cout<<x.i<<" "<<x.s<<endl; //输出空字符,说明x的值已经被x2接管了,x是一个可析构的状态
cout<<"---------"<<endl;
x.i = "aaa";
x.s = "bbb";
cout<<x.i<<" "<<x.s<<endl; // aaa bbb
return 0;
}

由于一个移后源对象具有不确定的状态,对其调用std::move是危险的。当我们调用move时,必须绝对确认移后源对象没有其他用户。

move用来将一个右值引用绑定到一个左值的标准库函数。调用move隐含地承诺我们将不会再使用移后源对象,除了销毁它或赋予它一个新值之外。

-------------本文结束感谢您的阅读-------------

本文地址:http://www.wangxinri.cn/2017/11/15/对象移动/
转载请注明出处,谢谢!

梦想夹带眼泪,咸咸的汗水!