C++语法学习笔记二十七: 引用折叠,转发、完美转发,forward

实例代码

// 引用折叠,转发、完美转发,forward

#include <iostream>

using namespace std;

template<typename T>
void myfunc(T &&tmprv) {   //万能引用
	cout << tmprv << endl;
	return;
}


void myfunc1(int v1, int v2){
	++v2;// 改变v2值,让其自加1
	cout << v1 + v2 << endl;
}


void myfunc2(int v1, int& v2){
	++v2;// 改变v2值,让其自加1
	cout << v1 + v2 << endl;
}


void myfunc3(int&& v1, int& v2){ // 一个右值参数 一个左值参数
	++v2;// 改变v2值,让其自加1
	cout << v1 << endl;
	cout << v2 << endl;
}


//通过函数模板把收到的参数以及这些参数相对应的类型(比如左值引用、右值引用、比如const)不变的转发给其他函数(转发给myfunc函数),这就叫转发。


template<typename F, typename T1, typename T2>
//myFuncTemp()相当于通过这个来转发。
void myFuncTemp(F f, T1 t1, T2 t2){ //F就是第三方我们要调用的函数, 就是要转发到的目标函数。
	f(t1, t2);
}

//改成万能引用
template<typename F, typename T1, typename T2>
void myFuncTemp1(F f, T1&& t1, T2&& t2){ //F就是第三方我们要调用的函数, 就是要转发到的目标函数。
	//针对main函数中的调用,myFuncTemp1(myfunc2, 30, j);
	// 推断出 : T1 = int , t1 = int &&
	// 推断出 : T2 = int&, t2 = int &
	f(t1, t2);
}


//改成万能引用 加std::forward
template<typename F, typename T1, typename T2>
void myFuncTemp2(F f, T1&& t1, T2&& t2){ //F就是第三方我们要调用的函数, 就是要转发到的目标函数。
	//针对main函数中的调用,myFuncTemp1(myfunc2, 30, j);
	// 推断出 : T1 = int , t1 = int &&
	// 推断出 : T2 = int&, t2 = int &
	f(std::forward<T1>(t1), std::forward<T2>(t2));
	//f(t1, t2);
}


void printfInfo(int &t){ //类型是左值引用的形参
	cout << "PrintfT()参数类型为左值引用" << endl;
}

void printfInfo(int &&t){ //类型是右值引用的形参
	cout << "PrintfT()参数类型为右值引用" << endl;
}

template <typename T>
void TestF(T && t) { //万能引用
	printfInfo(t);       // 如果t是右值,那么 T = int, t = int && ,但是t本身是左值
	// 如果t是左值,那么 T = int &, t = int &; t本身是左值
	printfInfo(std::forward<T>(t));
	printfInfo(std::move(t)); //左值转右值
}



int main(int argc, const char * argv[]) {

	//一: 引用折叠规则

	int i = 18;   // i的类型是int型, i本身是个左值
	myfunc(100);  // i是右值, T = int , tmprv = int &&
	myfunc(i);    // i是左值, T = int &, tmprv = int &


	// void myfunc(int& &&tmprv){...} // 我们模拟编译器,认为这是编译器应该给咱们实例化出来的myfunc的样子。
	// int& 是一组 , && tmprv是第二组
	// void myfunc(int& tmprv){...} //这个是编译器真正给我们实例化出来的myfunc模板函数的样子

	//由上面的分析,引出来C++ 11新标准,“引用折叠”
	//C++ 中有明确含义的引用只有两种,一种是&左值引用(类型), 一种是带&& 右值引用(类型)
	//void myfunc(int& &&tmprv) // 两组,第一组int& : 左值引用, 第二组 &&tmprv,实际是右值引用类型。

	//分成两组的, 第一组: 左值引用/右值引用    第二组: 左值引用/右值引用。 那么可以组合成4种情况
	//1. 左值引用 --- 左值引用    &      &
	//2. 左值引用 --- 右值引用    &      &&
	//3. 右值引用 --- 左值引用    &&     &
	//4. 右值引用 --- 右值引用    &&     &&

	//折叠规则:如果任意一个引用为左值引用,那么结果就为左值引用(传染),否则就是右值引用。
	//上面的4种组合折叠后为:
	//1. &
	//2. &
	//3. &
	//4. &&

	// (1.1) 引用的引用  : void func(int& &&i){ } // "i" : 引用的引用非法
	int b = 500;
	int &byi = b;  //byi是b引用
	//int & &byy = byi;   //非法  byy是byi的引用, 引用的引用
	//int & &byy = b;     //非法


	// 总结:引用的引用 是有这个概念的,编译器内部会使用到,编译器遇到“引用的引用”,会使用“引用折叠规则”来处理,但作为程序开发者不能使用“引用的引用”,也不可以这么写。


	//二:转发、完美转发
	int k = 50;
	myfunc2(41, k); //执行完成后 i 变成 51,这是对的
	int j = 70; //左值
	myFuncTemp(myfunc2, 30, j); //这里 j不是 71, 还是70,这说明 myFuncTemp()模板有问题
	//那么我们修改myFuncTemp模板函数, 让这个模板函数的参数能够保持 给定实参 的左值性(当然如果j有const属性,我们也希望const属性传递到myFuncTemp中去),这就需要万能引用了
	//万能引用: T&&  实参的所有信息都回传递到万能引用当中去,从而让编译器推导出来函数模板最终的形参类型。
	//T & ,则实参中只有const属性能传递到函数模板,而实参中的左值和右值性质就无法传递到函数模板中去,所以我们必须用万能引用 : T&&
	myFuncTemp1(myfunc2, 30, j); //这里 j是71 ,说明myFuncTemp1()模板传递引用成功


	myfunc3(20, j);
	int &&youzhi = 80; //右值引用绑右值
	//虽然&&youzhi 是绑定到右值的,但是youzhi本身是个左值
	//&&youzhi叫右值引用, youzhi是个左值(有地址);
	//youzhi是个左值,但是它的类型是右值引用

	//也就是说 无论左值引用这种概念还是右值引用这种概念,说得是youzhi的类型,而不是youzhi本身
	//如: void ft(int &&w){ ... } // 形参总是左值,(w总是左值),即使它的类型是右值引用。

	// myFuncTemp1(myfunc3, 30, j); //报错 提示不能将参数 从"int" 转换为“int &&”

	//上面的错误例子引出下面的概念:
	//完美转发:让我们可以写接受任意类型实参的函数模板,并将其转发到目标函数,目标函数会接收到与转发函数(myFuncTemp1)所接收到的完全相同(左值性、右值性都要保持)的实参。这就使用到std::forward
	myFuncTemp2(myfunc3, 30, j); //正确

	//三: std::forward
	//c++11 新函数 专门为转发而存在。它要么返回一个左值,要么返回一个右值
	//发挥作用的条件: 调用模板函数,模板函数参数是万能引用类型,模板函数负责转发。
	//std::forward的能力就是按照参数本来的类型转发
	//对std::forward函数的两种理解:
	//(1). 实参如果原来是左值,到了形参中还是左值,forward是按照实参原来的类型处理,所以std::forward之后还是个左值。
	//(2). 实参如果原来是个右值,到了形参中变成了左值,forward是按照实参原来的类型处理,所以std::forward之后就是个右值
	//所以,这里看起来forward有强制把左值转成右值的能力,所以forward其实只对原来是个右值的情况有用。
	//std::forward 的能力就是保持原始实参的类型(左值还是右值)
	TestF(2); // 2是右值   输出: 左值引用,右值引用,右值引用

	int q = 2;
	TestF(q);  // i是左值  输出: 左值引用,左值引用,右值引用

	//实参 是左值还是右值  这个信息会被保存到TestF里的万能引用里边的T类型中去的。

	//四: std::move 和 std::forward的区别
	//forward:  是强制把一个左值转成一个右值,但是如果实参原来就是左值,那forward什么也不做。(有条件的强制类型转换)
	//move: 无条件强制类型转换。

	//五: 再谈万能引用

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值