C++ 万能引用 与 完美转发

万能引用基本认识:

     万能引用是一种类型,就跟int是一种类型一个道理,再次强调,万能引用是一种类型。

读者都知道,右值引用是用符号&&来表示的。右值引用主要是绑定右值的。如

     int &&rv = 100;

现在举一个例子:

void myfunc(int &&tmprv)
{
   cout << tmpv <<endl;
   return;
}

在main主函数中添加代码:
myfunc(10);
int i = 10;
myfunc(i);    // error 右值引用不能绑定到左值。

    从以上代码得到一个结论:右值肯定不能接收左值。编译的时候给出错误的报错信息。

现在,将函数myfunc()改成函数模板:  

template<typename T>
void myfunc(T&& tmprv)
{
   cout << "tmprv" << endl;
   return ;
}

现在观察编译不在报错,看到的事实有两条

(1)这里的函数模板中的tmprv参数能接收左值,也能接收右值。

(2)tmprv的类型是T&&(这两个地址符是属于tmprv),编译都没报错。

现在再次观察一个事实,发现只有函数模板中发生类型模板参数推断时候,也就是推断这个T到底是什么类型的时候,才出现这种tmprv参数,既能接收右值,又能接收左值。即万能引用。

  万能引用又称为未定义引用。

  1)必须是函数模板

  2)必须是发生模板类型推导并且函数模板形参如 T&&

需要注意的是,万能引用T&&,虽然与右值引用很像,但是万能引用存在的场景要求T是类型模板参数。得到一个结论:T&& 是一个万能引用的类型。

template<typename T>
void func(T&& tmprv) {}       //万能引用

template<typename T>
void func(std::vector<T>&& param) {...}   //右值引用

完美转发

    完美转发,在转发的过程中,这些参数的某些类型的信息会丢失(如参数的const属性,左值、右值属性),显然这种转发就是不完美的,但是如果这些参数的类型信息可以原封不动从funcMiddle函数转发到funcLast函数,这种转发就叫作完美转发。

void funcLast(int v1,int v2)
{
    ++v2;
    cout << v1 + v2 <<endl;
}

在main函数中添加代码
int i = 50;
funcLast(41,i);    //正常显示结果92

可以注意到,在函数funcLast中修改了V2的值,并不影响调用者main()主函数中i的变量。

现在,再增加一个函数模板,这个函数模板的目的是把收到的函数以及这些参数对应的类型不变地转发给其他函数,这里通过一个接收参数的函数模板,把这些参数信息转发给目标函数,那么这个函数模板中参数所收到的参数类型是应该保持不变的,实参如果是const修饰,如果是左值或右值,这些信息都应该保持不变得转发给目标函数,那么怎样做到这些呢。

先写一个函数的模板

template<typename F,typename T1,typename T2>
void funcMiddle_Temp(F f,T1 t1,T2 t2)
{
   f(t1,t2);
}
在mian主函数中添加如下代码
int j = 70;
funcMiddle_Temp(funcLast,20,j);  

现在看来,funcMiddle_Temp()工作良好,那么修改下funcLast函数的参数,看看funcMiddle_Temp()是否还能够进行正确的转发吗,把funcLast()函数的第2个参数修改为一个引用:

void funcLast(int v1,int& v2)
{
   ++v2;
   cout << v1 + v2 <<endl;
}
在主函数main添加如下代码
int i = 50;
funcLast(41,i); //正常显示结果92.

但是如果把main()函数代码修改为:
int j = 70;
funcMiddle_Temp(funcLast,20,j);   /*正常显示结果91,但j不是71,是70*/

说明转发出现了问题,问题出在funcMiddle_Temp函数模板上,第三个参数t2,它显然会被推导成一个普通的int类型,而不是一个左值引用类型的int(int &)

所以,可以想一想这个funcMiddle_Temp()函数被实例化后的样子。

void funcMiddle_Temp(void(*f)(int, int &),int t1,int t2) {...}

那么怎样做才合适呢,此时万能引用T&&就出场了。

改造下funcMiddle_Temp()函数模板。

template<typename F,typename T1,typename T2>
void funcMiddle_Temp(F f,T1&& t1, T2&& t2) {....}

但是又有新的问题出现了,现在重写一个新的函数funcLast2()的函数,注意第1个函数的形参类型为int && (右值引用类型)

void funcLast2(int&& v1,int& v2)
{
    cout << v1 << endl;
    cout << v2 << endl;
}
在main主函数中添加如下代码
int j =70;
funcLast2(20,j);   //显示20.70 没问题

看起来没什么问题,但是如果通过funcMiddle_Temp()函数模板转发或中转一线看行不行,在主函数main中添加代码:

  funcMiddle_Temp(funcLast2,20,j);

此时编译失败报错"无法将参数从"T1"转换为'int &&' ". 这是因为funcMiddle_Temp的第二个参数虽然传的是右值,但进到funcMiddle_Temp函数里后就变成左值了。

那么怎么解决呢,就是用完美转发 std::forward了

template<typename F,typename T1,typename T2>
void funcMiddle_Temp(F f, T1&& t1,T2&& t2)
{

     f(std::forward<T1>(t1),std::forward<T2>(t2));
}

为了进一步加深对std::forward的理解,下面在进行一个范例演示。

namespace _nmsp2
{
	void printInfo(int& t)
	{
		cout << "printInfo()参数类型为左值引用" << endl;
	}
	void printInfo(int&& t)
	{
		cout << "printInfo()参数类型为右值引用" << endl;
	}
	void printInfo(const int& t)
	{
		cout << "printInfo()参数类型为const左值引用" << endl;
	}

	template <typename T>
	void TestF(T&& t) //万能引用
	{
		printInfo(std::forward<T>(t));
	}
}

在main函数添加如下代码

_nmsp2::TestF(1);  //1是右值
int i = 5;
_nmsp2::TestF(i);  //i是左值
_nmsp2::TestF(std::move(i)); //move:将左值转成右值
const int j = 8;
_nmsp2::TestF(j);  //j是const左值
_nmsp2::TestF(int(12)); //int(3)是临时对象所以是右值
int&& tmpvalue = 16;
_nmsp2::TestF(tmpvalue); //tmpvalue是左值

运行程序,结果如下:

printfInfo()参数类型为右值引用
printfInfo()参数类型为左值引用
printfInfo()参数类型为右值引用
printfInfo()参数类型为const左值引用
printfInfo()参数类型为右值引用
printfInfo()参数类型为左值引用

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水火汪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值