当前位置: 首页 > article >正文

【模板进阶】模板的万能引用

一、类型的区别和基本定义

1.1类型的基本定义

首先先看一个最简单例子:

//类型的区别和基本定义
void func(const int& abc) { } //abc的类型为const int&

这里的 a b c abc abc被推导为什么类型? 显然可见,为 c o n s t   i n t & const \ int\& const int&类型,上面不都写着呢!


然后,我们在看看一个简单的模板:

template<typename T>
void func(const T& abc) {}

void Test1() {
	func(10); //T为int,abc为const int&
}

这里传入的 10 10 10显然是转换为了 i n t int int类型,那么 T = i n t , a b c = c o n s t   i n t & T = int,abc = const\ int\& T=int,abc=const int&,这是容易推导出来的。


二、万能引用的基本认识

U n i v e r s a l   R e f e r e n c e Universal \ Reference Universal Reference后来被称为 F o r w a r d i n g   R e f e r e n c e   ( 转发引用 ) Forwarding \ Reference \ (转发引用) Forwarding Reference (转发引用),翻译成中文有好几种方式,这里我们采用一种最常用的名称:“万能引用”。

万能引用这个概念是许多概念的基础,所以这里率先将这个概念阐述清楚。首先记住一个结论:

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

2.1右值引用

万能引用和右值引用形式上长得相似,但实际上是两种类型,这里先介绍一下右值引用。


我们知道,右值是绑定在用 & & \&\& &&符号表示,主要是用于绑定右值,如:

//右值引用
void myfunc(int&& tmpr) {
	std::cout << tmpr << "\n";

}

测试一下:

void Test2() {
	myfunc(10); //正确,右值
	
	int i = 100;
 	myfunc(i); //错误,左值无法绑定到右值引用上
}

这里,容易发现, m y f u n c ( i ) myfunc(i) myfunc(i)这一行编译失败了,原因就是右值引用无法绑定在左值上。


2.2 万能引用

这里,我们改造一下 m y f u n c myfunc myfunc函数,把它写成一个函数模板:

template<typename T>
void myfunc(T&& tmpr) { 
	std::cout << "调用了函数模板:" << tmpr << "\n";
}

然后我们再同样调用相同的函数:

void Test2() {
	myfunc(10); //正确,右值
	
	int i = 100;
 	myfunc(i); //编译成功
}

可以发现,此时编译成功了。


现在,我们看到的事实有两条:

( 1 ) (1) (1)这里的函数模板中的 t m p r v tmprv tmprv参数能接受左值(作为实参),也能接受右值;
( 2 ) (2) (2) t m p r v tmprv tmprv的类型是 T & & T\&\& T&&(这两个地址符是属于 t m p r v tmprv tmprv的),编译都没报错。

实际上,这里使用到的就是万能引用,这里的 T T T类型在传入左值或者右值的时候,会被推断为不同的类型,通用编译期的折叠引用最终推断出正确的引用类型来接受参数。


同时,这里的引用类型是能够被修改的,如下:

//万能引用
template<typename T>
void myfunc(T&& tmpr) { 
	std::cout << "调用了函数模板:" << tmpr << "\n";
	
	tmpr = 0; //tmpr是左值,可以被修改
}
void Test1(){
	myfunc(i); //正确,万能引用
	std::cout << i << "\n";

	i = 200;
	myfunc(std::move(i));

	std::cout << i << "\n";
	}

我们调用一下,可以发现无论是左值还是右值,都可以被修改,因为接收实参的形参本身是一个左值,它可以被修改:

在这里插入图片描述


万能引用 ( U n i v e r s a l R e f e r e n c e ) (Universal Reference) (UniversalReference)又称为未定义引用,这种引用离不开上面提到的两种语境,这两种语境必须同时存在:

( 1 ) (1) (1)必须是函数模板;
( 2 ) (2) (2)必须是发生了模板类型推断并且函数模板形参形如 T & & T\&\& T&&


万能引用的格式为&T&&&。它也用两个地址符号 & & \&\& &&表示,所以,万能引用的形式就与右值引用一模一样。但是解释起来却不一样(注意语境,只有在语境满足的条件下,才能把 & & \&\& &&解释为万能引用而不是往右值引用)。

( 1 ) (1) (1)右值引用作为函数形参时,实参必须传递右值进去,不然编译器报错,上面已经看到了。

( 2 ) (2) (2)而万能引用作为函数形参时,实参可以传递左值进去,也可以传递右值进去。所以,万
能引用叫作未定义引用。如果传递左值进去,那么这个万能引用就是一个左值引用;如果传递右值进去,那么这个万能引用就是一个右值引用。从这个角度来讲,万能引用更厉害,是一种中性的引用,可以摇身一变,变成左值引用;也可以摇身一变,变成右值引用。


三、万能引用的辨认

万能引用和右值引用通常很相似,我们下面举几个例子来讨论一下:


3.1 例子1

这是右值引用,万用引用必须在模板的条件下:

//右值引用
void myfunc(int&& tmpr) {
	std::cout << tmpr << "\n";

}

3.2 例子2

这是右值引用,因为在类模板实例化的同时, T T T也就被确认了,因此是右值引用

template<typename T>
class myclass {
public:
	void func(T&& tmpr) { //不是万能引用
		std::cout << tmpr << "\n";
	}
};

3.3 例子3

这个是万能引用,这里的 U U U类型是独立的,不受类模板的影响

template<typename T>
class myclass {
public:
	template<typename U>
	void func2(U&& tmpr) { //万能引用
		std::cout << tmpr << "\n";
	}
};

3.4 例子4

形如 a u t o & & auto \&\& auto&&的也是万能引用:

//万能引用
template<typename T>
void myfunc(T&& tmpr) {
	auto&& tmpr2 = tmpr; //万能引用
	std::cout << "调用了函数模板:" << tmpr << "\n";
	
}

四、万能引用资格的剥夺

4.1剥夺

如果在万能引用中使用 c o n s t const const修饰,那么这个万能引用资格就会被剥夺,从而退化为右值引用,例如:

//剥夺

template<typename T>
void myfunc_const(const T&& tmpr) { //退化为右值引用
	std::cout << tmpr << "\n";
		
	//tmpr = 0; //const修饰不能被修改
}

void Test4() {
	int i = 100;
	//myfunc_const(i); //错误,只能传入右值

	myfunc_const(std::move(i)); //std::move转为右值

	std::cout << i << "\n";
}

这时,我们 m y f u n c _ c o n s t ( i ) myfunc\_const(i) myfunc_const(i)就会编译错误,因为此时已经是右值引用了。并且由于 c o n s t const const的修饰,我们无法在函数内修改参数。


http://www.kler.cn/news/310913.html

相关文章:

  • 基于深度学习的图像描述生成
  • 机器学习 vs. 深度学习
  • Apple M3编译MAVSDK安卓平台SO库
  • 如何在Android上实现RTSP服务器
  • 2024/9/19 408大题专训之五段式指令流水线题型总结
  • fastson与jackson入门
  • Windows本地制作java证书(与jeecgboot配置本地证书ssl问题)
  • 基于vue框架的宠物领养管理系统88v55(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
  • Python 数学建模——傅里叶变换时间序列分析
  • LeetCode 算法笔记-第 04 章 基础算法篇
  • Docker vs. containerd 深度剖析容器运行时
  • 【kafka-01】kafka安装和基本核心概念
  • CSP-J算法基础 树状结构与二叉树
  • C++笔记21•C++11•
  • PyRosetta Task介绍及示例代码
  • nginx基础篇(一)
  • 算法:双指针题目练习
  • MATLAB图像处理
  • CISP备考题库(八)
  • 术语“in law”(在分布上)
  • oracle表的类型
  • 当 PC 端和移动端共用一个域名时,避免 CDN 缓存页面混乱(nginx)
  • 基于MATLAB/Simulink的模型降阶方法介绍
  • Unity射击游戏开发教程:(36)敌人关卡生成器的设计和开发
  • 【STM32系统】基于STM32设计的DAC输出电压与ADC检测电压系统(简易万用表,检测电压电流)——文末工程资料下载
  • IP协议及相关特性
  • 理解AAC和Opus的编码与解码流程
  • 企业导师面对面,产教融合实训基地搭建人才成长快车道
  • 掌握RESTful API设计:构建高效、可扩展的Web服务
  • Android Studio报错: Could not find pub.devrel:easypermissions:0.3.0, 改用linux编译