【C++11】万能引用与完美转发

news2024/11/28 1:42:49

文章目录

  • 1. 模板中的&&—万能引用
  • 2. 完美转发及其应用场景
  • 3. 用到的代码
    • 3.1 string.h
    • 3.2 list.h
    • 3.3 test.cpp

1. 模板中的&&—万能引用

首先我们来看这样一段代码:

在这里插入图片描述
这里有4个函数,我们很容易能看出来它们是一个重载的关系
然后我们给这样一个函数模板
在这里插入图片描述
大家看这个函数模板的参数,T&& t

这里有两个&&,所以它是右值引用吗?

不是的!
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
我们实例化这个函数模板的时候
在这里插入图片描述
可以传左值,也可以传右值。
我们传的是左值,那参数t就是左值引用,我们传的是右值,参数t就是右值引用。
所以有些地方也把它叫做引用折叠,就是我们传左值的时候,它好像就把&&折叠了一下一样。
但是,大家看到我们这里接收t之后又往下传了一层
在这里插入图片描述
那大家就要思考一下在PerfectForward函数内部t又往下传给了Fun,那传给Fun的话t会匹配什么呢?

那我们来运行一下程序看看结果:

在这里插入图片描述
欸!怎么回事啊?
为什么全部匹配的都是左值引用啊!

那这里为什么会这样呢?

还记不记得上一篇文章里面又给大家提过一个东西:

就是右值不能取地址,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址
例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地
址,也可以修改rr1。
在这里插入图片描述
那可以取地址、可以赋值的话他不就变成左值了嘛。
所以,一个右值被右值引用后属性会变成左值
那想一想其实这样设计也是合理的:
比如这个场景
在这里插入图片描述
转移资源也可以认为是修改它了,而临时变量或匿名对象这样将亡值是不能修改的。

那再回过头来看我们上面的问题,大家就应该明白了

在这里插入图片描述
即使我们传过去的是右值,那它被右值引用之后也会变成左值,所以里面再传给Fun都匹配的是左值。

所以说:

模板的万能引用只是提供了能够同时接收左值和右值的能力,作用就是限制了接收的类型,但在后续使用中都退化成了左值。
但是有些场景下我们希望能够在传递过程中保持它的左值或者右值的属性,那要如何做到呢?
就需要用我们下面学习的完美转发

2. 完美转发及其应用场景

首先我们来看一个对应的场景:

我们之前模拟实现过list,搞一份过来
在这里插入图片描述
有些用不到的东西就给它删了。
配合我们自己搞的那个string,把string里面我们添进去的移动拷贝和移动构造我也先注释掉
我写这样一段代码
在这里插入图片描述
那我们之前的实现并没有移动语义,所以
在这里插入图片描述
全是深拷贝,不论左值还是右值。
那我们把string的移动构造和移动拷贝放出来,然后我给list的push_back增加右值引用的版本
在这里插入图片描述
但是我们的push_back复用了insert,所以insert也增加右值引用版本
在这里插入图片描述
再来运行
在这里插入图片描述
欸,怎么还不行,list的push_back还是都是深拷贝啊

问题出在哪里呢?

🆗,是不是就是我们上面提到的问题啊!
右值被右值引用后就变成了左值。
在第一次传递给push_back 的参数,右值的话就调用右值引用版本的push_back ,但是push_back里面调用insert第二次传递,就变成左值了
在这里插入图片描述
所以最终不论是右值还是左值的push_back,最终调的insert都是左值版本的,所以都是深拷贝

那如何解决这个问题呢?

如何在在传递过程中保持它的左值或者右值的属性呢?
这就要用到完美转发
std::forward 完美转发在传参的过程中保留对象原生类型属性
在这里插入图片描述
也是库里面提供的一个函数模板
那我们直接调用forward来保持参数的原生属性
在这里插入图片描述
那我们再来运行
在这里插入图片描述
哎呀,怎么还不行?
🆗,因为下面其实还有一层传递
在这里插入图片描述
insert里面创建新结点的时候要调用node的构造函数
在这里插入图片描述
所以这里也要加forward
但是!
在这里插入图片描述
node的构造函数我们只有左值引用的版本
所以,我们要再增加一个右值引用的版本
在这里插入图片描述
并且,这里_data的初始化我们也要用forward保持x它的属性,因为我们现在存string,他会调string的构造,这里保持它是右值,才会调到右值引用版本的移动拷贝
那这下
在这里插入图片描述
就可以了,右值的push_back就是移动拷贝了

那有了完美转发我们最开始那个场景:

在这里插入图片描述
都匹配的是右值引用的版本
怎么办?
加个完美转发就可以了
在这里插入图片描述

3. 用到的代码

3.1 string.h

#pragma once

namespace bit
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str)" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		// s1.swap(s2)
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}
		// 移动构造
		string(string&& s)
			:_str(nullptr)
		{
			cout << "string(string&& s) -- 移动拷贝" << endl;
			swap(s);
		}
		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);
			return *this;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		//string operator+=(char ch)
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string operator+(char ch)
		{
			string tmp(*this);
			tmp += ch;
			return tmp;
		}
		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
	string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		string str;
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;
			str += ('0' + x);
		}
		if (flag == false)
		{
			str += '-';
		}
		std::reverse(str.begin(), str.end());
		return str;
	}
}

3.2 list.h

#pragma once
#include<assert.h>

namespace yin
{
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _data;

		list_node(const T& x = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(x)
		{}

		list_node(T&& x = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(forward<T>(x))
		{}
	};


	// 1、迭代器要么就是原生指针
	// 2、迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> node;
		typedef __list_iterator<T, Ref, Ptr> self;
		node* _node;

		__list_iterator(node* n)
			:_node(n)
		{}

		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}

		self& operator++()
		{
			_node = _node->_next;

			return *this;
		}

		self operator++(int)
		{
			self tmp(*this);
			_node = _node->_next;

			return tmp;
		}

		self& operator--()
		{
			_node = _node->_prev;

			return *this;
		}

		self operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;

			return tmp;
		}

		bool operator!=(const self& s)
		{
			return _node != s._node;
		}

		bool operator==(const self& s)
		{
			return _node == s._node;
		}
	};


	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;

		iterator begin()
		{
			//iterator it(_head->_next);
			//return it;
			return iterator(_head->_next);
		}

		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

		const_iterator end() const
		{
			//iterator it(_head->_next);
			//return it;
			return const_iterator(_head);
		}

		void empty_init()
		{
			_head = new node(T());
			_head->_next = _head;
			_head->_prev = _head;
		}

		list()
		{
			empty_init();
		}

		template <class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_init();

			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		void swap(list<T>& tmp)
		{
			std::swap(_head, tmp._head);
		}

		list(const list<T>& lt)
		{
			empty_init();

			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}

		// lt1 = lt3
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				//it = erase(it);
				erase(it++);
			}
		}

		void push_back(const T& x)
		{
			insert(end(), x);
		}

		void push_back(T&& x)
		{
			insert(end(), forward<T>(x));
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		void pop_back()
		{
			erase(--end());
		}

		void pop_front()
		{
			erase(begin());
		}

		void insert(iterator pos, const T& x)
		{
			node* cur = pos._node;
			node* prev = cur->_prev;

			node* new_node = new node(x);

			prev->_next = new_node;
			new_node->_prev = prev;
			new_node->_next = cur;
			cur->_prev = new_node;
		}

		void insert(iterator pos, T&& x)
		{
			node* cur = pos._node;
			node* prev = cur->_prev;

			node* new_node = new node(forward<T>(x));

			prev->_next = new_node;
			new_node->_prev = prev;
			new_node->_next = cur;
			cur->_prev = new_node;
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());

			node* prev = pos._node->_prev;
			node* next = pos._node->_next;

			prev->_next = next;
			next->_prev = prev;
			delete pos._node;

			return iterator(next);
		}
	private:
		node* _head;
	};
}

3.3 test.cpp

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(forward<T>(t));
}

int main()
{
	PerfectForward(10); // 右值
	int a;
	PerfectForward(a); // 左值
	PerfectForward(move(a));// 右值

	const int b = 8;
	PerfectForward(b);// const 左值
	PerfectForward(move(b)); // const 右值

	return 0;
}

//#include "list.h"
//
//int main()
//{
//	yin::list<bit::string> lt;
//
//	bit::string s1("hello world");
//	lt.push_back(s1);
//
//	lt.push_back(bit::string("hello world"));
//	lt.push_back("hello world");
//
//	return 0;
//}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1042118.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows下libmodbus 支持upd库的编译与Qt里的调用

一. libmodbus 支持udp版库下载 https://github.com/systemmonkey42/libmodbus/tree/udp_support 二. MSYS2编译工具安装 &#xff08;1&#xff09;下载MSYS2并安装 下载地址&#xff1a;https://www.msys2.org/ 双击该exe&#xff0c;安装msys2 (2) 安装编译所需库 在软件安…

js惰性函数 ----如何让函数执行之后只执行函数某一部分

看下面这份ts代码 实现的效果也很简单,就是将一份文本,复制到剪切板上,未了兼容更多的浏览器(没错说的就是你>ie !),做了一个兼容性判断, 当浏览器支持navigator.clipboard这个api时,就直接调用这个api将文本复制到剪切板中, 如果不支持这个api的话,就执行else里面的代码,这…

微信小程序 request合法域名 中设置WebSocket的wss/ws报错 该域名协议头非法 问题解决

微信小程序并不支持直接用wss://格式去设置域名 但其实我们可以用缓兵之计 这里 我们直接改成 https的 然后 我们照常通过 wss://ws-api.turingapi.com/api/v2去链接 可以看到 他这里依旧连接成功了

Spring MVC 之 Restful 风格请求⽀持

Tips&#xff1a; REST⻛格请求是什么样的&#xff1f;SpringMVC对REST⻛格请求到底提供了怎样的⽀持 在 Web 系统中&#xff0c;前端通过 HTTP 请求给后端传递参数有四种方式&#xff0c;可以将参数放在请求路径、Query 参数、HTTP 协议头、HTTP 协议体中。而放在协议体中的…

五,修改.hdr插件

由于辐照度贴图是.hdr格式&#xff0c; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width, height, 0, GL_RGB, GL_FLOAT, data); osg的.hdr插件需要修改&#xff0c;否则格式不对。 即在ReaderWriterHDR::readImage()中修改&#xff0c; bool convertToRGB8 true;int pixel…

【iOS逆向与安全】DTRpcClient 抓包和代码分析记录

frida-trace -UF -m "-[DTRpcConfig isAMRPC]"onEnter(log, args, state) {log(-[DTRpcConfig isAMRPC] ~~~~~);var customObj new ObjC.Object(args[0]); // 自定义对象// 打印该对象所有属性var ivarList customObj.$ivars;for (key in ivarList) {log(key${key…

【SpringBoot】| SpringBoot集成Dubbo

目录 一&#xff1a;SpringBoot集成Dubbo 1. 创建公共项目 2. 创建提供者项目provider 3. 创建消费者consumer项目 4. 注册中心Zookeeper的安装 图书推荐&#xff1a;《Python 自动化办公应用大全》 一&#xff1a;SpringBoot集成Dubbo 阿里巴巴提供了 dubbo 集成 spring…

原来,这就是现货黄金投资最大的悲哀

在现货黄金投资之中&#xff0c;最大的悲哀是什么呢&#xff1f;首先要知道的是现货黄金投资中最大的悲哀并不是亏损&#xff0c;比如投资者会问我都亏损了&#xff0c;为什么不是最大的悲&#xff1f;哎&#xff0c;不就是因为想进行现货黄金投资&#xff0c;就是想获利的吗&a…

北斗导航系统为渔船保驾护航,助力海洋渔业发展

在无垠的海洋中&#xff0c;渔船扮演着举足轻重的角色&#xff0c;为人们带来美味的海鲜。然而&#xff0c;每一次渔船出海都充满了未知和危险&#xff0c;船只的安全成为了渔民和国家关注的焦点。幸运的是&#xff0c;北斗导航系统作为一项顶级技术正在为渔船保驾护航&#xf…

肖sir___环境的讲解详情__002

一、环境讲解 1、jdk 什么是JDK&#xff1f;JDK的作用&#xff1f; JDK是java语言的软件开发工具包&#xff0c;能解释java程序&#xff0c;编译java语言&#xff0c;没有jdk的话无法编译Java程序。 包含了各种类库和工具&#xff0c;机器不是直接识别语言的&#xff0c;会借助…

零信任沙盒

零信任沙盒分析对比 互联网早期的沙盒&#xff08;sandbox&#xff09;又译为沙箱&#xff0c;意为在计算机安全领域中构建一种安全机制&#xff0c;为运行中的程序提供的隔离环境。通常用来测试一些来源不可信、具破坏力或无法判定的程序&#xff0c;特别是病毒木马类的程序。…

塔望食研院|千亿食用油市场拐点,量减价增,竞争加剧!

自2022年12月塔望咨询开设塔望食品大健康消费研究院&#xff08;简称塔望食研院&#xff09;栏目以来&#xff0c;塔望食研院以“为食品行业品牌高质量发展赋能”为理念&#xff0c;不断发布食品大健康行业研究、消费研究报告。塔望食研院致力于结合消费调研数据、企业数据、第…

XC8233 电容式单按键触摸检测 IC 广泛应用于 TWS及 DC 应用上,实现产品智能化

目前市面上很多的小家电和消费类电子都已经改成触摸式的按键功能&#xff0c;而XC8233是一款电容式单按键触摸检测及接近感应控制芯片。采用 CMOS 工艺制造&#xff0c;内建稳压和去抖动电路&#xff0c;高可靠性&#xff0c;专为取代传统按键开关而设计。超低功耗与宽工作电压…

Java笔记:手写spring之简单实现springboot

手写spring之简单实现springboot 仓库地址: Raray-chuan/mini-spring 博文列表: 导读手写spring之ioc手写spring之aop手写spring之简单实现springboot 1.springmvc框架的理解 什么是MVC: MVC就是一个分层架构模式: MVC 设计模式一般指 MVC 框架&#xff0c;M&#xff08…

ALM物联网管理平台助力中台上云 数字化转型让展示更直观清晰

支持移动浏览、支持大屏显示等功能&#xff0c;能够为设备厂家提供数据依据&#xff0c;方便厂家的售后以及产品的维护、为运维等相关公司提供运维管理等相关功能。 ALM物联网云平台是基于以往的物联网产品&#xff0c;以及目前市场上的各种云平台优点&#xff0c;研精心打造的…

车联网安全集智联盟正式成立

2023年9月22日&#xff0c;在工业和信息化部网络安全管理局支持下&#xff0c;2023年世界智能网联汽车大会——“集智创新车联网安全新格局”特色专场在北京举行。工业和信息化部网络安全管理局领导出席并致辞&#xff0c;中国工程院邬江兴院士以及来自政产学研用等各方的领导和…

TLS/SSL(一)科普之加密、签名和SSL握手

一 背景知识 感悟&#xff1a; 不能高不成低不就备注&#xff1a; 以下内容没有逻辑排版,仅做记录 https基础面经 ① 加密方式 说明&#xff1a; 单向和双向认证遗留&#xff1a; 如何用openssl从私钥中提取公钥? ② 互联网数据安全可靠条件 说明&#xff1a; 二者相…

Java核心-你真的知道Object吗(Object:所有类的超类)

作者&#xff1a;逍遥Sean 简介&#xff1a;一个主修Java的Web网站\游戏服务器后端开发者 主页&#xff1a;https://blog.csdn.net/Ureliable 觉得博主文章不错的话&#xff0c;可以三连支持一下~ 如有需要我的支持&#xff0c;请私信或评论留言&#xff01; 前言 今天来聊一聊…

医院陪诊小程序源码 陪诊陪护小程序源码

医院陪诊小程序源码 陪诊陪护小程序源码 近年来&#xff0c;随着互联网技术的不断发展&#xff0c;我们的生活中出现了越来越多的智能设备和智能应用&#xff0c;这些智能应用不仅极大方便了我们的生活&#xff0c;还对现代医疗服务体验产生了深远的影响。本文将为大家介绍一种…

轻松搭建Linux的环境

Linux的环境的搭建 目录&#xff1a;一、使用云服务器二、使用虚拟机软件2.1 下载虚拟机软件2.2 下载一个操作系统的镜像文件 三、直接安装在物理机上四、使用XShell远程登录到Linux 目录&#xff1a; 我们平常用的都是windows系统&#xff0c;对Linux系统还是很陌生得。为什么…