l 左值与右值的区别
C++中左右值的区分标准为:是否能够取地址,如果能取地址则为左值,否则为右值。
左值:具名变量、对象;
右值:C++11中将右值分为:将亡值(xvalue,expiring value)和纯右值(prvalue,pure value),纯右值:非引用返回的临时变量、原始字面量、运算表达式产生的临时变量和lambda表达式;
将亡值:与右值引用相关联的表达式,如将被移动的对象,T&&函数返回值,std::move返回值。
l 右值引用特性
引用类型为一个对象的别名,因此无论左值引用还是右值引用均在声明时必须进行初始化。右值引用有时可为左值,有时可为右值。如:
template<typename T>
void print(T &&i)
int i = 1;
print(i);//左值引用也可以正常调用
print(1);
但如果声明如下:
void print(int &&i);
int i = 1;
print(i);//报错:左值不能被正常调用
print(1);
模板类型的右值引用为一个未知的引用类型,该类型可推导为左值,也可以推导为右值。是否所有带有模板类型的右值引用都存在这种特质呢?答案是否定的。综合类型(universalreference)只能在T&&情况下发生,其他任何附加条件都会失效,而变成一个普通的右值引用。
template<typename T>
void print(const T &&i)//这样就不允许
template<typename T>
void print(std::vector<T> &&i)
auto i = {1};
print(i);//这样也不允许
右值引用特性总结如下:
1) T&&作为模板参数时,如被左值X初始化,则T的类型为X&;如被右值X初始化,则T的类型为X。
2) auto&& 或函数参数类型自动推导的T&&是一个未定的引用类型,被称为综合类型(Universal referece),它可能是左值引用也可能是右值引用,这取决于初始化的值类型。
3) 所有的右值引用叠加到右值引用仍然是一个右值引用,其他引用折叠都为左值引用。当T&&为模板参数时,输入左值为左值引用,输入右值则为右值引用。
编译器会将已命名的右值引用视为左值,而将未命名的右值引用视为右值。
l 右值引用作用
右值引用一个最大的作用就是实现移动构造函数(move construct)或赋值函数将右值直接引用到左值中,提高性能,避免深度拷贝。
A(A&& a):m_ptr(a.m_ptr)//引用同一块内存,避免重新分配内存
{
a.m_ptr = nullptr;
cout << "move constructor" << endl;
}
A& operator=(A&& a) {//引用同一块内存,避免重新分配内存
if(this != &a) {
delete m_ptr;
m_ptr = a.m_ptr;
}
return *this;
}
l 右值引用转换
从上面的示例可以看出,编译器会将已命名的右值引用视为左值,也就是右值引用参数作为下一次调用时被视为左值而无法进行右值调用,为解决这个问题,C++中提供move函数将左值转换为右值。
void forward(int &&j)
{
print(std::move(j));//调用print(int &&i)
}
如何实现综合类型(universal references)的参数转发呢?这个参数如为左(右)值传递则进行左(右)值转发。C++中提供forward来满足此语义。
template<typename T>
void forward(T &&j)
{
print(std::forward<T>(j));
}
int i = 1;
forward(i);//左值转发
forward(2);//右值转发