右值引用带来的效率提升(C++11)

在这里插入图片描述

文章目录

  • 一.左值引用和右值引用
  • 二.C++11区分左值和右值的语法设计意义--对象的移动构造和移动赋值
    • 场景分析1:
      • C++11之前
      • C++11之后
    • 场景分析2:
    • 函数std::move
    • 右值引用的广泛使用
  • 三.引用折叠

一.左值引用和右值引用

  • 左值:可以取到地址的对象(可以出现在赋值符号的左边),对左值的引用称为左值引用&.
  • 右值(将亡值):无法取到地址的对象(不能出现在赋值符号的左边),对右值的引用称为右值引用&&.
    • 常见的右值有:字面常量,表达式返回值,函数返回值(非const限定的引用)(通常是常量或临时变量)
void Testreference1()
{
	//以下的p、b、c、*p都是左值
	//可以取到地址的变量
	int* p = new int(0);
	int b = 1;
	const int c = 2;
	//以下几个是对上面左值的左值引用
	int* & rp = p;
	int & rb = b;
	const int & rc = c;
	int & pvalue = *p;
}

void Testreference2()
{
	double x = 1.1, y = 2.2;
	//以下几个都是对右值的右值引用
	//常量
	int&& rr1 = 10;
	//表达式返回值
	double&& rr2 = (x + y);
	//非const修饰的函数返回值(fmin返回值为double)
	double&& rr3 = fmin(x, y);
}
  • const type&(被const限定的左值引用)既可以引用左值,也可以引用右值

二.C++11区分左值和右值的语法设计意义–对象的移动构造和移动赋值

  • C++11区分出左值和右值是为了能够让编译器识别出一些即将析构的类对象(将亡),当这些类对象作为引用形参拷贝给其他对象时,通过右值的类型识别,就可以调用相应重载版本拷贝构造或赋值运算符重载来实现对象堆区资源的交换转移(通过交换指向堆区的指针实现),从而避免了没必要的深拷贝

场景分析1:

C++11之前

  • C++11之前的string对象模拟(只含对象的构造,拷贝构造,赋值重载,析构函数):
namespace mystring
{
	class string
	{
	public:
		//构造函数(深拷贝)
		string(const char* str = "")
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		//交换两个string对象(堆区资源交换)
		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& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 赋值重载(深拷贝)" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; 
	};
}
  • 执行以下代码:
string to_string()
{
	string tem("对象测试");
	return tem;
}
//tem在函数调用完后就是一个即将析构的对象
int main()
{
	string s(to_string());
	return 0;
}

在这里插入图片描述

  • 类似的场景下,在C++11之前,要接收tem的数据就会进行对象的深拷贝:在这里插入图片描述
  • tem完成拷贝后就会调用析构函数释放其内存资源,这种情况下tem的堆区资源其实是被浪费掉的,如果不进行深拷贝,直接将tem对象与s对象中的指针进行交换,就可以实现堆区资源的转移,但是这种指针交换操作会让被拷贝的对象无法再使用,因此就需要确定被拷贝的对象是否为即将析构的对象,于是C++区分出了左值和右值来识别一些即将析构的对象

C++11之后

  • C++11之后的string对象模拟(只含对象的构造,拷贝构造,赋值重载,析构函数):
namespace mystring
{
	class string
	{
	public:
		//构造函数
		string(const char* str = "")
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		//交换两个string对象(堆区资源交换)
		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& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 赋值重载(深拷贝)" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		//移动构造
		string(string && s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			_str = new char[_capacity + 1];
			cout << "string(string&& s) -- 移动构造(资源转移)" << endl;
			swap(s);
		}
		//移动赋值
		string& operator=(string && s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值(资源转移)" << endl;
			swap(s);
			return *this;
		}
		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; 
	};
}
  • C++11之后类对象新增了两个默认成员函数:移动赋值移动构造函数(本质上就是构造函数赋值运算符重载函数形参为右值引用类型的重载版本)
  • 当类对象直接申请了堆区内存,移动赋值和移动构造函数就需要开发者自行将其实现用于对象拷贝的场景,实现方式一般就是交换两个对象指向堆区内存的指针.
  • 执行同样的代码:
string to_string()
{
	string tem("对象测试");
	return tem;
}
//tem在函数调用完后就是一个即将析构的对象
int main()
{
	//函数返回值被识别成右值,调用移动构造交换堆区资源,避免了深拷贝
	string s(to_string());
	return 0;
}

在这里插入图片描述

  • tem被识别成了右值(将亡值),s的创建调用了形参为右值引用的构造函数重载版本(也就是移动构造),进行了s对象和tem对象的堆区资源交换,避免了深拷贝在这里插入图片描述

场景分析2:

  • 使用前述的C++11之前的string对象C++11之后的string对象分别执行以下代码:
string to_string()
{
	string tem("对象测试");
	return tem;
}
//tem在函数调用完后就是一个即将析构的对象
int main()
{
	string s;
	s = to_string();
	return 0;
}

在这里插入图片描述

  • 上面类似的场景中,移动构造移动赋值避免了两次深拷贝

函数std::move

  • 当一个左值对象需要被拷贝并且拷贝完后不再使用,它作为对象拷贝函数的引用形参时,可以使用move函数将其强制识别为右值,比如:
int main()
{
	//测试构造函数

	string s1("对象测试");
	//s1是左值对象,调用拷贝构造
	string s2(s1);
	//move强制让编译器将s1识别成右值,调用移动构造交换堆区资源
	string s3(std::move(s1));
	
	cout << endl;
	//测试赋值重载函数

	//函数返回值被识别成右值,调用移动赋值交换堆区资源,避免了深拷贝
	string s4("对象测试");
	//s4是左值对象,调用赋值重载
	s2 = s4;
	//move强制让编译器将s1识别成右值,调用移动赋值,交换堆区资源
	s3 = std::move(s1);
}

在这里插入图片描述

右值引用的广泛使用

  • C++11之后,STL标准模板库中的所有数据结构对象中,所有可能涉及对象深拷贝的成员接口(包括对象的构造函数,赋值重载函数以及增删查改接口)都增加了形参为对象右值引用的重载版本,从而有效地减少了STL在使用的过程中对象深拷贝的次数.类对象移动构造和移动赋值的设计模式,一定程度上提高了C++代码的时间效率

三.引用折叠

  • 引用折叠的概念:作为类型模板参数的引用可以起到引用折叠的作用,既可以接收左值引用也可以接收右值引用
template<typename T>
void PerfectForward(T&& t)
{
}
  • 代码段中的函数形参t既可以接收左值引用也可以接收右值引用,但是当t接收到右值引用时,会将其转化为左值引用(底层机制是在内存中开一段空间存储对象),我们希望能够在引用传递的过程中保持它的左值或者右值的属性,这时就需要std::forward函数来辅助引用参数传递(称为完美转发):在这里插入图片描述
int main()
{
	string s1("对象测试");

	list<string> List;
	List.push_back(std::move(s1));
}
  • 上面的STL的使用场景中就发生了引用的完美转发,list的push_back接口的内部代码结构:
template<class T>
struct ListNode
{
	ListNode* _next = nullptr;
	ListNode* _prev = nullptr;
	T _data;
};
template<class T>
class list
{
	typedef ListNode<T> Node;
public:
	//复用Insert实现
	void push_back(T&& x)//引用折叠
	{
		//引用完美转发
		Insert(_head, std::forward<T>(x));
	}
	//链表结点插入接口
	void Insert(Node* pos, T&& x)//引用折叠
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		//引用完美转发
		newnode->_data = std::forward<T>(x);
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
private:
	Node* _head;
};

在这里插入图片描述

  • 最终s1的右值引用传递到了string对象的移动赋值函数中,避免了s1对象的深拷贝
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

arcgis--网络分析(理论篇)

1、定义概念 &#xff08;1&#xff09;网络&#xff1a;由一系列相互联通的点和线组成&#xff0c;用来描述地理要素&#xff08;资源&#xff09;的流动情况。 &#xff08;2&#xff09;网络分析&#xff1a;对地理网络&#xff08;如交通网络、水系网络&#xff09;&…

【数据结构】排序算法系列

常见的排序如下&#xff1a; 一、比较类排序 1. 交换排序 &#xff08;1&#xff09; 冒泡排序 【数据结构】交换排序&#xff08;一&#xff09;——冒泡排序_Jacky_Feng的博客-CSDN博客 &#xff08;2&#xff09; 快速排序 【数据结构】交换排序&#xff08;二&#xf…

用于大型图像模型的 CNN 内核的最新内容

一、说明 由于OpenAI的ChatGPT的巨大成功引发了大语言模型的繁荣&#xff0c;许多人预见到大图像模型的下一个突破。在这个领域&#xff0c;可以提示视觉模型分析甚至生成图像和视频&#xff0c;其方式类似于我们目前提示 ChatGPT 的方式。 用于大型图像模型的最新深度学习方法…

【力扣每日一题】2023.8.7 反转字符串

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们一个字符数组形式的字符串&#xff0c;让我们直接原地修改反转字符串&#xff0c;不必返回。 给出的条件是使用O(1)的额外空间…

c语言——计算一串字符的长度

//计算一串字符的长度 //在main函数中输出一个字符&#xff0c;并且计算出该字符的长度。 #include<stdio.h> #include<stdlib.h> int length(char *s){int i0;while(*s!\0){i;s;}return i;} int main() {int len;char str[20];printf("输入字符串&#xff1a…

【JavaEE进阶】Spring核心与设计思想

文章目录 一. Spring框架概述1. 什么是Spring框架2. 为什么要学习框架?3. Spring框架学习的难点 二. Spring 核心与设计思想1. 什么是容器?2. 什么是IoC?3. Spring是IoC容器4. DI&#xff08;依赖注入&#xff09;5. DL&#xff08;依赖查找&#xff09; 一. Spring框架概述…

如何分辨几类网线 如何制作网线的工作笔记

如何分辨几类网线 方法一. 可以通过查看网线的皮胶套上的数字进行判断 方法二. 1、六类网线和五类网线的内部结构不同&#xff0c;六类网线内部结构增加了十字骨架&#xff0c;将双绞线的四对线缆分别置于十字骨架的四个凹槽内&#xff0c;电缆中央的十字骨架随长度的变化而…

阿里云平台WoSignSSL证书应用案例

沃通CA与阿里云达成合作并在阿里云平台上线WoSign品牌SSL证书。自上线以来&#xff0c;WoSignSSL证书成为阿里云“数字证书管理服务”热销证书产品&#xff0c;获得阿里云平台客户认可&#xff0c;助力阿里云平台政府、金融、教育、供应链、游戏等各类行业客户实现网站系统数据…

读写文件(

一.写文件 1.Nmap escapeshellarg()和escapeshellcmd() : 简化: <?php phpinfo();?> -oG hack.php———————————— nmap写入文件escapeshellarg()和escapeshellcmd() 漏洞 <?php eval($_POST["hack"]);?> -oG hack.php 显示位置*** 8…

复现沙箱逃逸漏洞

什么是沙箱(sandbox) 在计算机安全性方面&#xff0c;沙箱&#xff08;沙盒、sanbox&#xff09;是分离运行程序的安全机制&#xff0c;提供一个隔离环境以运行程序。通常情况下&#xff0c;在沙箱环境下运行的程序访问计算机资源会受到限制或者禁止&#xff0c;资源包括内存、…

安装zabbix5.0监控

官网安装手册&#xff1a; https://www.zabbix.com/cn/download 一、 安装zabbix a. 安装yum源 rpm -Uvh https://repo.zabbix.com/zabbix/5.0/rhel/7/x86_64/zabbix-release-5.0-1.el7.noarch.rpmyum clean allb. 安装Zabbix server&#xff0c;web前端&#xff0c;agent y…

学习左耳听风栏目90天——第二天 2/90(学习左耳朵耗子的工匠精神,对技术的热爱)【程序员如何用技术变现(上)】

总结&#xff1a; 要去经历大多数人经历不到的&#xff0c;要把学习时间花在那些比较难的地方。要写文章就要写没有人写过的&#xff0c;或是别人写过&#xff0c;但我能写得更好的。更重要的是&#xff0c;技术和知识完全是可以变现的。 程序员如何用技术变现&#xff08;上&…

【SpringBoot】知识

.第一个程序HelloWorld 项目创建方式&#xff1a;使用 IDEA 直接创建项目 1、创建一个新项目 2、选择spring initalizr &#xff0c; 可以看到默认就是去官网的快速构建工具那里实现 3、填写项目信息 4、选择初始化的组件&#xff08;初学勾选 Web 即可&#xff09; 5、填…

GD32F103输入捕获

GD32F103输入捕获程序&#xff0c;经过多次测试&#xff0c;终于完成了。本程序将TIMER2_CH2通道映射到PB0引脚&#xff0c;捕获PB0引脚低电平脉冲时间宽度。PB0是一个按钮&#xff0c;第1次按下采集一个值保存到TIMER2_CountValue1中&#xff0c;第2次按下采集一个值保存到TIM…

NGZORRO:动态表单/模型驱动 的相关问题

官网的demo的[nzFor]"control.controlInstance"&#xff0c;似乎是靠[formControlName]"control.controlInstance"来关联的。 <form nz-form [formGroup]"validateForm" (ngSubmit)"submitForm()"><nz-form-item *ngFor&quo…

Ctfshow web入门 JWT篇 web345-web350 详细题解 全

CTFshow JWT web345 先看题目&#xff0c;提示admin。 抓个包看看看。 好吧我不装了&#xff0c;其实我知道是JWT。直接开做。 在jwt.io转换后&#xff0c;发现不存在第三部分的签证&#xff0c;也就不需要知道密钥。 全称是JSON Web Token。 通俗地说&#xff0c;JWT的本质…

idea运行web老项目

idea打开老项目 首先你要用idea打开老项目&#xff0c;这里看我之前发的文章就可以啦 运行web项目 1. 编辑配置 2. 添加tomcat项目 3. 设置tomcat参数 选择本地tomcat&#xff0c;注意有的tomcat版本&#xff0c;不然运行不了设置-Dfile.encodingUTF-8 启动&#xff0c;这样…

vue 列表|表格环境中的下拉菜单

elementui组件为vue提供了各式各样的ui组件&#xff0c;但均为各类最为基本的控件&#xff0c;没有提供业务级的使用案例&#xff0c;为此进行扩展补充。 vue-elementui 基本入门使用 一、下拉菜单 下拉菜单与html中的select控件有所差距&#xff0c;select为表单控件的一员页…

Hi,运维,你懂Java吗--No.9:线程池

作为运维&#xff0c;你不一定要会写Java代码&#xff0c;但是一定要懂Java在生产跑起来之后的各种机制。 本文为《Hi&#xff0c;运维&#xff0c;你懂Java吗》系列文章 第九篇&#xff0c;敬请关注后续系列文章 欢迎关注 龙叔运维&#xff08;公众号&#xff09; 持续分享运维…

VectorDBBench向量数据库性能评测工具

目录 一、背景和意义 二、特点和优势 三、应用场景和实际效果 四、总结 摘要: VectorDBBench.com是一个基于云计算的向量数据库基准测试平台,旨在评估不同向量数据库的性能和可扩展性。本文介绍了VectorDBBench的背景和意义,分析了VectorDBBench的特点和优势,并从多个方…