万能引用:
万能引用是应用在模板类中的一种特殊的引用类型,它有如下作用:
- 如果你传递给万能引用的是一个左值或者左值引用,那么编译器将把万能引用推导为左值引用。
- 如果你传递给万能引用的是一个右值或者右值引用,那么编译器将把万能引用推导为右值引用。
template<class T>
void Show(T &&n)
{
cout << n << endl;
}
引用折叠:
引用折叠,就是说C++中不允许出现引用的引用,如果出现了怎么办,比如在万能引用中,那么我们就要消除掉多余的引用,变成单引用,如何做到呢?就是c++定制了一套规则,这套规则就是引用折叠。
直接看代码:
一般的模板函数的T类型是如何推导的:
#include<iostream>
#include<typeinfo>
using namespace std;
template<typename T>
void Show(T c)//按值传参,舍弃传入类型的引用类型
{
cout << "&c: " << &c << endl;
}
int main()
{
int a = 10;
cout << "&a: " << &a << endl;
int& b = a;
cout << "&b: " << &b << endl;
Show(b);
return 0;
}
#include<iostream>
#include<typeinfo>
using namespace std;
template<typename T>
void Show(T& c)//按引用传参,保留传入参数的引用类型
{
cout << "&c: " << &c << endl;
}
int main()
{
int a = 10;
cout << "&a: " << &a << endl;
int& b = a;
cout << "&b: " << &b << endl;
Show(b);
return 0;
}
也就是说T最后推导出来的类型是否带引用跟你传入的参数类型是否带引用无关,跟你定义T的时候是否带引用有关,也就是你是按值传参还是按引用传参,如果是T则会丢弃其引用类型,如果是T&则会保留其引用类型。
如果我们使用万能引用呢?
如果用了万能引用,不管你传的类型是左值还是左值引用,最后T都会被推导成左值引用,也就是int& &&a不管你传的值是右值还是右值引用,最后T都会被推导成右值引用,也就是int&& &&。我们知道c++不允许有引用的引用,这时候就需要引用折叠,引用折叠的规则就是只要有一个左值引用,最后推导的结果肯定是左值类型,即int& &&最后会变成int &,右值引用跟右值引用在一起会推导成右值引用,即int&& &&a会变成int &&a。
还有int&& &的情况,这个情况应该是T接收到的是右值引用的情况。比如说:
#include<iostream>
#include<typeinfo>
using namespace std;
template<typename T>
void Show(T& c)//按值传参
{
}
int main()
{
int&& a = 5;
Show(a);
return 0;
}
因为模板中的参数用了&,所以保留了其引用类型,T推导出来的类型是int&&而不是int。
完美转发:
为什么要有完美转发?
举个例子
#include <iostream>
void foo(int& x) {
std::cout << "foo(int& x) is called" << std::endl;
}
void foo(int&& x) {
std::cout << "foo(int&& x) is called" << std::endl;
}
template <class T>
void bar(T x) {
foo(x);
}
int main() {
bar(5); //输出:"foo(int& x) is called"
int a = 5;
bar(a); //输出:"foo(int& x) is called"
}
我们知道,bar(5)中的5进入bar函数,T会被推导成int类型,而不是int&&,然后再经过引用折叠,最后x的类型为int &&,但是此时的函数内的x再作为参数调用foo,调用的就是void foo(int &x)而不是void foo(int &&x),这是因为我们在函数体内使用x的时候,我们是使用已经存在的对象,是有确切地址的,而不是创造一个临时的对象,此时的x将会被当成左值处理,即作为参数的时候是int&&,但是在函数体内的时候是int&,当左值来用,但这不意味者x的类型发生了改变,还是int&&,但是在使用的时候是当作左值来用,也就是这个参数的类型再传递的过程中发生了变化,这可能是我们不想看见的,这时完美转发就是为了解决这种问题,实现参数是什么类型,我们用的时候就是什么类型。
如何使用完美转发?
使用forward<Type>(variable),Type指的是要完美转发的参数,variable指的是要将该参数绑定到哪个变量上,哪个变量要使用这个参数。
template<class Type>
void Test(Type && t)
{
cout << t << endl;
}
template<class Type>
void Show(Type &&t)
{
Test(std::forward<Type>(t));
}
一般来说上面的三个是一起搭配使用的,使用了万能引用就要考虑各种参数传进来最终推导出来的类型是什么样的,这个时候就需要懂得引用折叠的规则,推导除了最终类型,还需考虑参数类型到了函数体内会不会发生改变,有没有右值引用到了函数体被当成左值引用的情况,如果这种情况发生了怎么办,如果我们不希望这种情况发生,则要使用万能引用。