左值引用vs右值引用
左值引用:&
int num = 10;
int &b = num; //num为左值 正确
int &c = 10; //错误,左值引用不能初始化为右值
右值引用:&&
和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化,比如:
int num = 10;
int && a = num; //错误,不能用左值初始化右值引用
int && a = 10; //正确
移动构造函数
move函数(左值强制转成右值)
我们知道,当使用类的右值对象(可以理解为临时对象)初始化同类对象时,编译器会优先选择移动构造函数
,相比拷贝构造少了复制和析构
但是使用类的左值对象(有名称,能获取其存储地址的实例对象)就无法使用移动构造函数了,如果我们想用怎么办呢?——利用move将左值强制转成右值
move函数的功能:将某个左值强制转化为右值
引用折叠
引用折叠的规则就是:任意一个引用为左值引用时结果均为左值,只有在两个引用均为右值引用时才是右值引用
如下:
- 左值-左值 T& & ——》左值
- 左值-右值 T& && ——》左值
- 右值-左值 T&& & ——》左值
- 右值-右值 T&& && ——》右值
万能引用
什么是万能引用?
万能引用是依赖引用折叠而实现的,可以使得函数入参同时支持左值参数和右值参数
什么意思呢?先来看这样一段代码,fun函数同时支持了参数为左值和右值的情况
#include <iostream>
using namespace std;
// 接收左值
void fun(int& lvalue)
{
std::cout << "lvalue = " << lvalue << std::endl;
}
// 接收右值
void fun(int&& rvalue)
{
std::cout << "rvalue = " << rvalue << std::endl;
}
int main()
{
int x = 10;
fun(x); // 左值
fun(10); // 右值
fun(std::move(x)); // std::move(x) == static_cast<int&&>(x) 将左值转发为右值
}
这样虽然可以实现同时支持参数为左值和右值,但是每次需要重载两个函数实在是麻烦,有没有办法将两个函数写在一个里面呢?
——有办法,万能引用!!
万能引用常见形式如下:
template<typename T>
void fun(T&& param)
{
// do something
}
利用引用折叠的原理:
- 如果传左值,假设为int&,传入参数中的类型就为int& &&,之后经由引用折叠导致最终等价于int&。
- 如果传右值,假设为int&&,传入参数中的类型就为int&& &&,之后经由引用折叠导致最终等价于int&&
完美转发forward
现在有了万能引用。当我们既需要接收左值类型,又需要接收右值类型的时候,再也不用分开写两个重载函数了。
那么这到底有啥用呢?什么情况下,我们需要一个函数,既能接收左值,又能接收右值呢?
——答案就是:转发的时候
什么是完美转发?
完美转发是指:保持原来的值属性不变的一种能力
即:如果原来的值是左值,经std::forward处理后该值还是左值;如果原来的值是右值,经std::forward处理后它还是右值。
举例理解:
#include <iostream>
template<typename T>
void print(T & t){
std::cout << "左值" << std::endl;
}
template<typename T>
void print(T && t){
std::cout << "右值" << std::endl;
}
template<typename T>
void testForward(T && v){
print(v);
print(std::forward<T>(v));
print(std::move(v));
}
int main(int argc, char * argv[])
{
testForward(1);
std::cout << "======================" << std::endl;
int x = 1;
testFoward(x);
}
运行结果:
左值
右值
右值
=========================
左值
左值
右值
从上面第一组的结果我们可以看到,传入的1虽然是右值,但经过函数传参之后它变成了左值(在内存中分配了空间);
而第二行由于使用了std::forward函数,所以不会改变它的右值属性,因此会调用参数为右值引用的print模板函数;
第三行,因为std::move会将传入的参数强制转成右值,所以结果一定是右值。
再来看看第二组结果
因为x变量是左值,所以第一行一定是左值;
第二行使用forward处理,它依然会让其保持左值,所以第二也是左值;
最后一行使用move函数,因此一定是右值。
总结:通过引用折叠,我们实现了万能引用。在万能引用内部,利用forward函数,本质上是又利用了一遍引用折叠,实现了完美转发。
forward实现原理
forward实现源码:
template <typename T>
T&& forward(typename std::remove_reference<T>::type& param)
{
return static_cast<T&&>(param);
}
template <typename T>
T&& forward(typename std::remove_reference<T>::type&& param)
{
return static_cast<T&&>(param);
}
forward实现了两个模板函数,一个接收左值,另一个接收右值。在上面有代码中:
typename std::remove_reference<T>::type
其含义就是获得去掉引用的参数类型。所以上面的两上模板函数中,第一个是左值引用模板函数,第二个是右值引用模板函数。
紧接着std::forward模板函数对传入的参数进行强制类型转换,转换的目标类型符合引用折叠规则,因此左值参数最终转换后仍为左值,右值参数最终转成右值。