一、左值和右值
1、左值:具有存储性质的对象,要占用内存空间、内存地址;位于赋值运算符左边时可以赋值(同时也可以用在右边)
2、右值:没有存储性质的对象, 也就是临时对象。位于赋值运算符左边时不可赋值。
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是一个斜体样式常量引用,引用到一个右值,但引用本身是一个持久对象(可以对其取地址),为左值。
二、左值引用
int a = 5;
int& v1 = a; //非常量左值引用,只能绑定到非常量左值,不能绑定到常量左值以及任何右值
const int& v2 = 5; //常量左值引用,可以绑定到所有类型的值
三、常量左值引用
const int& v1 = 5;
const int v2 = 5;
int &v3 = 5;//错误,需要左值
第一句v1的过程如下:
int temp = int(5);
const int& v1= temp;
v1 v2两者的区别是:前者直接使用了右值并为其“续命”,而后者的右值在表达式结束后就销毁了。
作函数参数时:
一般从一个函数返回一个局部对象的引用是不对的,如:
T & my_op ( void )
{
T t;
return t;
}
特殊情况:返回一个常引用
const T & my_op ( void )
{
T t;
return t;
}
const T & my_t_obj = my_op ();
在这个情况下,局部变量 t 不会被直接析构,而是会保留到 my_t_obj 的生命周期结束为止
总之,C++常量引用语法上可以引用一个临时变量。这种方法在使用引用作函数参数和返回局部变量时有意义。常量引用主要用在作函数参数或保证不修改原变量的时候。
四、右值引用
右值引用所引用的是右值,因此一般为临时变量,标记为T &&。/右值引用只能绑定到右值
int && num = 8; //右值引用
正常情况下,右值”8“在表达式语句结束后,其生命也就终结了(通常我们也称其具有表达式生命期),而通过右值引用的声明,该右值又“重获新生”,其生命期将与右值引用类型变量num的生命期一样。只要num还“活着”,该右值临时量将会一直“存活”下去。
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;
注: 基于安全考虑,具有名字的声明为右值的参数不会被认定为右值。
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语义
//执行深拷贝
String(const String& rhs): data_(new char[rhs.size() + 1])
{
strcpy(data_, rhs.c_str());
}
这里进行了内存分配和拷贝数据,如果rhs是个临时对象,要是能将rhs的数据“move”到data_中岂不是提高了运行效率,这样子你即不需要为data_重新分配内存,又不需要去释放rhs的内存,简直两全其美。
在C++11中,右值引用就可以用来干这事:
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构造函数)。
对于强制被强制转化为右值的左值,其生命周期并不会有所改变,但是由于它的值已经转移给右值引用了,所以我们应该保证不再使用它的值,否则将会出错,我们能做的就是给它赋新值或者销毁它。