C++11 浅谈 右值引用和move语义
2016-01-24 18:49
295 查看
一、左值和右值
lvalue: 具有存储性质的对象,要实际占用内存空间、内存地址;位于赋值运算符左边时可以赋值(同时也可以用在右边)。rvalue:没有存储性质的对象, 也就是临时对象。位于赋值运算符左边时不可赋值。
例子:
[code]int a = 10; int b = 20; int *pFlag = &a; vector<int> vctTemp; vctTemp.push_back(1); string str1 = "hello "; string str2 = "world"; const int &m = 1;
a和b都是持久对象(可以对其取地址),是左值;
a+b是临时对象(不可以对其取地址),是右值;
a++是先取出持久对象a的一份拷贝,再使持久对象a的值加1,最后返回那份拷贝,而那份拷贝是临时对象(不可以对其取地址),故其是右值;
++a则是使持久对象a的值加1,并返回那个持久对象a本身(可以对其取地址),故其是左值;
pFlag和*pFlag都是持久对象(可以对其取地址),是左值;
vctTemp[0]调用了重载的[]操作符,而[]操作符返回的是一个int &,为持久对象(可以对其取地址),是左值;
100和string(“hello”)是临时对象(不可以对其取地址),是右值;
str1是持久对象(可以对其取地址),是左值;
str1+str2是调用了+操作符,而+操作符返回的是一个string(不可以对其取地址),故其为右值;
m是一个常量引用,引用到一个右值,但引用本身是一个持久对象(可以对其取地址),为左值。
二、左值引用
[code]int a = 5; int& v1 = a; //非常量左值引用,只能绑定到非常量左值,不能绑定到常量左值以及任何右值 const int& v2 = 5; //常量左值引用,可以绑定到所有类型的值
对于常量左值引用:
[code]const int& v1 = 5; const int v2 = 5;
两者的区别是:前者直接使用了右值并为其“续命”,而后者的右值在表达式结束后就销毁了。
三、右值引用
右值引用所引用的是右值,因此一般为临时变量,标记为T &&。[code]int && num = 8; //右值引用
正常情况下,右值”8“在表达式语句结束后,其生命也就终结了(通常我们也称其具有表达式生命期),而通过右值引用的声明,该右值又“重获新生”,其生命期将与右值引用类型变量num的生命期一样。只要num还“活着”,该右值临时量将会一直“存活”下去。
[code] //右值引用只能绑定到右值 int a = 5; const int b = 5; int&& v1 = 5; int&& v2 = a; //compile error,a为左值 int&& v3 = b; //compile error,b为左值 int&& v4 = a + b;
注: 基于安全考虑,具有名字的声明为右值的参数不会被认定为右值。
[code]bool is_r_value(int &&) { return true; } bool is_r_value(const int &) { return false; } void test(int && i) { bool isRValue = is_r_value(i); // i为有名字的变量,即使声明为右值也不会被认为是右值。false }
四、move语义
我们看以下代码:[code]//执行深拷贝 String(const String& rhs): data_(new char[rhs.size() + 1]) { strcpy(data_, rhs.c_str()); }
这里进行了内存分配和拷贝数据,如果rhs是个临时对象,要是能将rhs的数据“move”到data_中岂不是提高了运行效率,这样子你即不需要为data_重新分配内存,又不需要去释放rhs的内存,简直两全其美。
在C++11中,右值引用就可以用来干这事:
[code]String(String&& rhs): data_(rhs.data_) { rhs.data_ = nullptr; }
上面是一个move拷贝构造函数,它并没有进行深拷贝,而是将rhs.data_这个指针的所有者转移到了另一个对象,并将rhs的data_指针置为空。这样子,对于临时值我们只需要做浅拷贝,而避免了深拷贝带来的性能损失问题。
在 C++11,一个std::vector的 “move 构造函数” 对某个vector的右值引用可以单纯地从右值复制其内部 C-style 数组的指针到新的 vector,然后留下空的右值。这个操作不需要数组的复制,而且空的临时对象的析构也不会摧毁内存。如果vector没有 move 构造函数,那么复制构造函数将被调用,这时进行深拷贝。
move语义:将对象资源的所有权从一个对象转移到另一个对象,没有内存的拷贝。
C++11还提供了std::move方法来将左值转换为右值,从而普通的左值也可以借助move语义来优化性能。
注意,如果是一些基本类型比如int和char [10]定长数组类型,使用move仍然会发生拷贝(因为没有对应的move构造函数)。
对于强制被强制转化为右值的左值,其生命周期并不会有所改变,但是由于它的值已经转移给右值引用了,所以我们应该保证不再使用它的值,否则将会出错,我们能做的就是给它赋新值或者销毁它。
相关文章推荐
- C语言指针要点
- 一起talk C栗子吧(第一百一十回:C语言实例--线程标识符)
- c++ primer
- 初识C++(一)
- c++之路进阶——斜率优化形如DP[i]=f[j]+x[i](f[j]只与j变量有关)的问题(Print Article)
- (模拟) uva 1589 Xiangqi
- 数塔 (HDU_2084) 基础DP
- 最大连续子序列(HDU_1231) 基础DP
- C语言scanf函数详细解释
- memset函数使用详解
- 2014年第五届蓝桥杯B组(C/C++)预赛题目及个人答案(欢迎指正)
- C#调用C++动态库并回调C#函数
- C#调用C++动态库并回调C#函数
- C语言删除目录
- C++字符串相关
- 转载_推荐!国外程序员整理的 C++ 资源大全
- c语言学习之分配不足引起的错误
- 2014年第五届蓝桥杯C/C++程序设计本科B组省赛 地宫取宝(编程大题)
- 2014年第五届蓝桥杯C/C++程序设计本科B组省赛 蚂蚁感冒(编程大题)
- 重学C++ (八) 复制控制