万能引用与完美转发
- 万能引用
- 完美转发
- 完美转发的应用场景
- 万能引用的一个小点
万能引用
注意:当&&出现在模板中时,不是右值引用,而是叫万能引用。比如下面的T&& t
template<typename T>
void PerfectForward(T&& t)
{Fun(t);
}
万能引用:
-
当实参是左值,那么
T&& t
就是个左值引用有些地方,也会叫做引用折叠
什么意思呢?就是原本形参是
T&& t
,但比如当实参是个int类型的变量,那么形参这里的模板参数就会变成:int& t
。原本两个&&,现在变成一个&。因此形象的称为是引用折叠 -
当实参是右值,那么
T&& t
就是个右值引用
为什么会有万能引用呢?归根结底是因为,这里的T是通过形参t推导出来的,假如实参是10,那么T就是
int&&
;假如实参是int类型的变量,那么T就是int&
完美转发
- 问题引出
我们看如下代码:
void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }template<typename T>
void PerfectForward(T&& t)
{Fun(t);
}int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}
打印结果:
为什么打印结构都是左值引用呢?第一、三、五不都传的是右值吗?
- 原因分析
关键问题出在了这里:T&& t
右值引用会被编译器识别为左值。即这里的t是个左值
因此,在void PerfectForward(T&& t)
中,实参传递到t中,不论实参是右值还是左值,t始终是个左值。然后再Fun(t);
,调用的也当然都是左值引用的函数
- 深入思考
假如我想要实参是右值t就是右值,实参是左值t就是左值呢?那么就需要用到完美转发
完美转发:在传参的过程中保留对象原生类型属性。完美转发的实现需要用到库里面的函数forward()
也就是说,右值引用会被默认识别为左值,如果想让其保持原本的属性,那么就用完美转发
对上面的例子,加上完美转发后,结果如下:
完美转发的应用场景
看如下例子:(右值引用中的场景二)
int main()
{list<xy::string> it;it.push_back("111");return 0;
}
C++11之前, 上述代码的调用过程:
C++11后, “111”是个右值,因此在红框处可以移动拷贝。所以调用过程变成了下面:
到这里就和我们在右值引用中所讲的过程一样。但是其实上述代码是存在问题的
这是因为:首先"111"传给push_back(T&& x)
没有问题,但此时x默认是个左值,然后insert(end(),x)
这一调用,并不能调用到右值引用版本的insert,后面的Node(x)
也不会调用到右值引用版本的list_node
构造函数。所以最终也不是移动拷贝,而是深拷贝
完美转发的出现,很好的解决了上述问题,完美只需如下修改:
让每个右值引用形参都保留其原本属性,这样最终就可以调用到移动拷贝
万能引用的一个小点
在完美转发的应用场景中:
list_node的构造函数会有两个版本,一个是左值引用的版本,一个是右值引用的版本:
template<class T>
struct list_node
{list_node<T>* _prev;list_node<T>* _next;T _val;list_node(const T& val):_prev(nullptr), _next(nullptr), _val(val){}list_node(T&& val): _prev(nullptr), _next(nullptr), _val(forward<T>(val)){}
};
在万能引用中说到:模板中出现&&不是右值引用,而是万能引用。那这里的list_node(T&& val)
是万能引用吗?如果是的话,那么list_node(const T& val)
这个左值引用的函数是不是就没有存在的必要了?
其实这里不是万能引用,list_node(const T& val)
也不能删去
因为我们说的万能引用最核心的因素在于,模板参数T是可以通过形参val推出来的,而这里的T并不是通过val推导出来的,而是在这里
就确定了的
但是我们只需如下:
template<class T>
struct list_node
{list_node<T>* _prev;list_node<T>* _next;T _val;template<class Ty>list_node(Ty&& val): _prev(nullptr), _next(nullptr), _val(forward<Ty>(val)){}
};
那么就可以使得其变成万能引用(类模板中再用函数模板)