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

文章目录

  • 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 oncenamespace 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 = lt3list<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.mzph.cn/news/88140.shtml

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

相关文章

CSI及CPHY的学习知识点

0.CSI早期只有DPHY可用 CSI-2 v1.3及之后版本提供了更高的接口带宽和更好的通道布局灵活性。从CSI-2 V1.3开始引入了C-PHY 1.0&#xff08;C-PHY 1.0是MIPI联盟于2014年9月发布的新物理接口)&#xff0c;能够兼容之前的D-PHY v1.2版本。 在CSI-2 V1.2及以前都只能用DPHY传输csi…

echarts添加点击事件

实现效果&#xff1a;点击图表&#xff0c;弹出该数据下对应得详情 官方文档&#xff1a; 封装的图表组件中&#xff1a; 点击获取点击得对象&#xff0c;进而将需要的参数传给父组件&#xff0c;在父组件中再去请求接口获取更多信息 this.chart.on(click, (params)> {th…

HTML5福利篇--使用Canvas画图

目录 一.Canvas元素 1.Canvas元素定义 2.使用JavaScript获取页面中的Canvas对象 二.绘制图形 1.绘制直线 2.绘制矩形 &#xff08;1&#xff09;rect() &#xff08;2&#xff09;strokeRect() &#xff08;3&#xff09;fillRect()和clearRect()函数 3.绘制圆弧 4.…

C语言的stdio.h的介绍

C语言的stdio.h的介绍 C语言的stdio.h的介绍 C语言的stdio.h的介绍C语言stdio.h的介绍 C语言stdio.h的介绍 这个含义是导入标准输入输出库 包含头文件.h&#xff0c;std标准库&#xff0c;io是input output输入输出库 <>代表系统库&#xff0c;自定义的话用""…

Stm32_点灯

利用HAL库基本语法实现C8T6点灯操作 引脚配置 PB3、4 、5 //设置为output PB6、7 设置Input且为上拉初始化代码&#xff1a; 由于3、4、5引脚默认输出为0灯泡默认点亮所以要将他们初始化为1 void Inint(){//初始化灯泡函数熄灭HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PI…

C#源码 LIS实验室(检验科)信息系统源码 SaaS模式的Client/Server架构

LIS实验室&#xff08;检验科&#xff09;信息系统&#xff0c;一体化设计&#xff0c;与其他系统无缝连接&#xff0c;全程化条码管理。集申请、采样、核收、计费、检验、审核、发布、质控、查询、耗材控制等检验科工作为一体的网络管理系统。 技术细节&#xff1a; 体系结构…

【效率提升】maven 转 gradle 实战 | 京东云技术团队

一、灵魂三问 1、gradle 是什么&#xff1f; 一个打包工具&#xff0c; 是一个开源构建自动化工具&#xff0c;足够灵活&#xff0c;可以构建几乎任何类型的软件&#xff0c;高性能、可扩展、能洞察等。其中洞察&#xff0c;可以用于分析构建过程中数据&#xff0c;提供分析参…

Vue 使用vue-pdf 显示pdf文件 切换页面 缩放 全屏 自动播放等

<template><div id"container"><!-- 上一页、下一页--><div class"right-btn"><div click"toFullOrExit" class"turn-btn"><span>{{ isFull 1 ? "取消全屏" : "全屏" }}&l…

IDS与防火墙的区别

1. 什么是IDS&#xff1f; IDS是入侵检测系统&#xff08;Intrusion Detection System&#xff09;的缩写。它是一种计算机安全工具&#xff0c;用于监视计算机网络或系统中的活动&#xff0c;以便检测潜在的恶意行为或入侵尝试。IDS的主要目标是识别可能威胁网络安全的活动&a…

Serverless 数仓技术与挑战(内含 PPT 下载)

近期&#xff0c;Databend Labs 联合创始人张雁飞发表了题为「Serverless 数仓技术与挑战」的主题分享。以下为本次分享的精彩内容&#xff1a; 主题&#xff1a; 「Serverless 数仓技术与挑战」 演讲嘉宾&#xff1a; 张雁飞 嘉宾介绍&#xff1a; Databend Labs 联合创始人…

智能井盖:提升城市井盖安全管理效率

窨井盖作为城市基础设施的重要组成部分&#xff0c;其安全管理与城市的有序运行和群众的生产生活安全息息相关&#xff0c;体现城市管理和社会治理水平。当前&#xff0c;一些城市已经将智能化的窨井盖升级改造作为新城建的重要内容&#xff0c;推动窨井盖等“城市部件”配套建…

微信公众号开发(BUG集)

1.微信公众平台接口错误:不合法的自定义菜单使用用户 地址&#xff1a;解决地址 2.微信公众平台接口错误:invalid ip 180.101.72.196 ipv6 ::ffff:180.101.72.196, not in whitelist rid: 6511420b-60c59249-01084d02 白名单离开放服务器IP

Mybatis学习笔记9 动态SQL

Mybatis学习笔记8 查询返回专题_biubiubiu0706的博客-CSDN博客 动态SQL的业务场景&#xff1a; 例如 批量删除 get请求 uri?id18&id19&id20 或者post id18&id19&id20 String[] idsrequest.getParameterValues("id") 那么这句SQL是需要动态的 还…

【初阶数据结构】——堆的引入

目录 前言 一、二叉树的顺序结构及实现 1.1二叉树的顺序结构 1.2堆的结构 二、堆的实现 2.1堆向上调整算法&#xff08;堆的插入&#xff09; 2.2堆向下调整算法&#xff08;堆的删除&#xff09; 2.3建堆的时间复杂度 2.4堆的创建 2.5堆的初始化和空间的销毁 2.6堆…

二值贝叶斯滤波计算4d毫米波聚类目标动静属性

机器人学中有些问题是二值问题&#xff0c;对于这种二值问题的概率评估问题可以用二值贝叶斯滤波器binary Bayes filter来解决的。比如机器人前方有一个门&#xff0c;机器人想判断这个门是开是关。这个二值状态是固定的&#xff0c;并不会随着测量数据变量的改变而改变。就像门…

Python 序列排序

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 &#x1f447; &#x1f447; &#x1f447; 更多精彩机密、教程&#xff0c;尽在下方&#xff0c;赶紧点击了解吧~ python源码、视频教程、插件安装教程、资料我都准备好了&#xff0c;直接在文末名片自取就可 python中&…

【QandA C++】内存泄漏、进程地址空间、堆和栈、内存对齐、大小端和判断、虚拟内存等重点知识汇总

目录 内存泄漏 内存模型 、进程地址空间 堆和栈的区别 内存对齐 大端小端及判断 虚拟内存有什么作用 内存泄漏 概念: 是指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况, 内存泄漏并不是指内存在物理上的消失, 而是应用程序分配了某段内存后, 因为设计错误…

Docker 安装Redis(集群)

3主3从redis集群配置 1、新建6个docker容器 redis 实例 docker run -d --name redis-node-1 --net host --privilegedtrue -v /data/redis/share/redis-node-1:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6381 docker run -d --name redis-node-2 --ne…

2023 “华为杯” 中国研究生数学建模竞赛(E题)深度剖析|数学建模完整代码+建模过程全解全析

​ 问题一 血肿扩张风险相关因素探索建模 思路&#xff1a; 根据题目要求,首先需要判断每个患者是否发生了血肿扩张事件。根据定义,如果后续检查的血肿体积比首次检查增加≥6 mL或≥33%,则判断为发生了血肿扩张。 具体判断步骤: (1) 从表1中提取每个患者的入院首次影像检查…

python基础语法

目录 常量和表达式 变量和类型 1.整数int 2.小数float 3.字符串string 4.布尔类型bool 5.动态类型 注释 输入输出 输出 输入 运算符 算术运算符 关系运算符 逻辑运算符 赋值运算符 python和C、Java语法区别 创建一个python项目 常量和表达式 在python中&…