C++右值引用 移动语义 完美转发 引用叠加

news2024/10/7 20:30:11

右值引用

  • MyString
    • 浅拷贝与深拷贝
    • 浅赋值与深赋值
  • 左值与右值
    • 左值概念
    • 左值右值与函数的结合
    • 移动构造函数
    • 移动赋值函数
    • 移动构造和移动赋值的应用
  • 移动语义 有点问题
  • 完美转发
  • 引用叠加

MyString

浅拷贝与深拷贝

s1先在堆区申请了空间,然后将p指针指向的字符串复制到该堆区空间,
拷贝构造函数是s2指向了s1指向的空间,即指向了同一个堆区空间,会导致函数析构时,会对同一个空间释放两次,这就是浅拷贝
在这里插入图片描述

class MyString
{
private:
	char* str;
public:
	//strlen(p)只算了字符的长度,没有算上'\0'
	MyString(const char* p = nullptr) :str(nullptr)
	{
		if (nullptr != p)
		{
			int len = strlen(p) + 1;
			str = new char[len];
			strcpy(str, p);
			//strcpy_s(str,len,p);  //C11 g++ 不支持
		}
		else
		{
			//相当一个空字符串
			str = new char[1];
			*str = '\0';
		}
	}
	~MyString()
	{
		delete[]str;
		str = nullptr;
	}
	MyString(const MyString& s) :str{s.str} {
		cout << "Copy Create MyString :" << this << endl;
	}
};
int main() {
	MyString s1("yhpinghello");
	MyString s2(s1);
	return 0;
}

所以需要进行深拷贝,s2先申请堆区空间,然后再将原来s1的内容拷贝过去,这样析构s1,s2时就不会相互影响。
在这里插入图片描述

MyString(const MyString& s) :str{nullptr} {
	int len = strlen(s.str) + 1;
	str = new char[len];
	strcpy(str, s.str);
	cout << "Copy Create MyString :" << this << endl;
}

浅赋值与深赋值

MyString& operator=(const MyString& it) {
	this->str = it.str;
	return *this;
}

这个代码有两个问题,第一个是内存泄漏,str指向了it.str,那么str原本指向空间就找不到了,就无法释放了
第二个问题就是,这两个指针指向同一个地方,当调用析构函数释放空间时,当s2释放了他的空间后,s1仍然指向该空间,此时s1就是一个失效指针
浅赋值
在这里插入图片描述
深赋值
就是把s2指向的堆区资源释放,重新开辟一个和s1同样大小的堆区空间,再把s1内容拷贝过来
深赋值和深拷贝构造,的区别就是,多了一步,释放以前的资源

MyString& operator=(const MyString& it) {
	if (this != &it) {
		delete[]this->str;
		int len = strlen(it.str) + 1;
		str = new char[len];
		strcpy(str, it.str);
	}
	return *this;
}

在这里插入图片描述

在这里插入图片描述

按照值返回,会构建将亡值对象,调用拷贝构造函数,深拷贝,回到主函数,调用深赋值函数,这个程序看,堆区开辟了三次,后文用移动拷贝,移动赋值优化。

MyString func(const char* p) {
	MyString tmp(p);
	return tmp;
}
int main() {
	
	MyString s1("tulun");
	s1=func("yhpinghello");
	cout << s1 << endl;
	return 0;
}

在这里插入图片描述
如果用引用返回的话,不构建将亡值,实质上就是将tmp的地址,用eax存着,再析构func,然后回到主函数,就是解引用了一次,将tmp深赋值给s1,但是tmp已经被释放了,从已经死亡的对象捞数据,这是不允许的

MyString& func(const char* p) {
	MyString tmp(p);
	return tmp;
}
int main() {
	
	MyString s1("tulun");
	s1=func("yhpinghello");
	cout << s1 << endl;
	return 0;
}

在这里插入图片描述
所以以值返回和引用返回具有巨大的差异,一般不要用引用返回。

左值与右值

左值概念

能取地址的量,对象,为左值
不能取地址为右值

将亡值:计算所产生的临时量,将亡值没有名字,就是右值,如果将亡值有名字就是泛型左值
纯右值:字面常量,由内置类型和指针运算产生的都是纯右值
类型加括号调用构造函数,创建不具名对象,就是右值
在这里插入图片描述
a++ 这是右值 返回的是不具名对象,
++a 这是左值 返回的是a本身

int main() {
	MyString s1("yhping");//Left value
	MyString& rs = s1;//左值引用

	MyString("tulun");//不具名对象,右值
	MyString &&rsa=MyString("tulun");//右值引用,引用不具名对象
	return 0;
}
int main() {
	const int& a = 10;
	//int tmp=10;
	//const int&a=tmp;
	
	//a += 10;//error

	int&& rr = 10;
	//int tmp=10;
	//int&&rr=tmp;
	rr += 10;
	return 0;
}

左值右值与函数的结合

void func(MyString &s){}//left ref
void func(const MyString &cs){}//const left ref
void func(MyString &&rs){}//right ref
int main(){
	MyString s1("yhping");//Left value
	func(s1);
	return 0;
}

func(s1);中s1是一个左值,首先匹配void func(MyString &s){},没有该引用,退而求其次,匹配void func(const MyString &cs){},如果没有,就会报错,因为不能用s1去初始化右值引用

func(MyString("tulun"));

这里优先和右值引用匹配,没有的话,就和常左值引用匹配,再没有的话,不能匹配左值。

const MyString s1("yhping");//

只能和常性左值匹配,不能和左值匹配,不能和右值匹配

MyString&&rs=MyString("dataprint");
&rs

这里rs是右值引用,rs是不具名对象的别名,rs可以取地址,rs失去右值概念,所以这里变成了左值

常性左值引用是万能引用

移动构造函数

s1不就没有了

MyString(MyString&& s) {//移动拷贝构造
	this->str = s.str;
	s.str = nullptr;
}
int main()
{
	MyString s1("yhping");
	MyString s2(std::move(s1));
	return 0;
}

move的作用就是将左值强转为右值
在这里插入图片描述
将s1的堆区资源移动给了s2

任何时候delete free一个空指针,都是安全的,它会先判断一下

移动赋值函数

问题:s2不就没有了
将堆区的地址移动给s1

MyString& operator=(MyString&& s)
{
	if (this == &s)return *this;
	delete[]str;
	str = s.str;
	s.str = nullptr;
	return *this;
}
int main()
{
	MyString s1("hello");
	MyString s2("yhping");
	s1=std::move(s2);
	//s1=(MyString&&)s2;
	return 0;
}

在这里插入图片描述
如果是深构造和深赋值
在这里插入图片描述
移动构造和移动赋值在实现上的区别还是,赋值需要先释放资源,防止内存泄漏

移动构造和移动赋值的应用

对于没有加static,const修饰的局部对象,return的时候直接将tmp转为右值,调用移动构造,s就是tmp一个别名,移动拷贝构造一个将亡值对象,将tmp的资源转移到将亡值对象,回到主函数,将亡值是右值,调用移动赋值,s就是将亡值的别名
用移动构造和移动赋值,创建对象的次数没有改变,但是对堆区的操作很简答,只用开辟一个空间。

MyString& operator=(MyString&& s)//移动赋值
{
	if (this == &s)return *this;
	delete[]str;
	str = s.str;
	s.str = nullptr;
	return *this;
}
MyString(MyString&& s) {//移动构造
	this->str = s.str;
	s.str = nullptr;
}
MyString func(const char* p) {
	MyString tmp(p);
	return tmp;
}
int main() {
	
	MyString s1("hello");
	s1=func("yhping");
	cout << s1 << endl;
	return 0;
}

在这里插入图片描述

移动语义 有点问题

move实际上并不能移动任何东西,它唯一的功能是将一个左值强制转换为一个右值,使我们可以通过右值引用使用该值,以用于移动语义。强制转换为右值的目的是为了方便实现移动构造。

using namespace std;
template<class _Ty>
struct my_remove_reference {
	using type = _Ty;
	my_remove_reference() {
		type x;
	}
};
template<class _Ty>
struct my_remove_reference<_Ty&> {
	using type = _Ty;
	my_remove_reference() {
		type x;
	}
};
template<class _Ty>
struct my_remove_reference<_Ty&&> {
	using type = _Ty;
	my_remove_reference() {
		type x;
	}
};

int main() {
	my_remove_reference<int>();
	my_remove_reference<int&>();
	my_remove_reference<int&&>();
}

x 都是整型

template<class _Ty>
using my_remove_reference_t=typename my_remove_reference<_Ty>::type
int main() {
	my_remove_reference_t<int>x;
	my_remove_reference_t<int&>y;
	my_remove_reference_t<int&&>z;
	return 0;
}

xyz都是整型

template<class _Ty>
void fun(_Ty&& t) {
}
int main() {
	int a = 10;
	const int b = 20;
	fun(a);  //   int &
	fun(b);   //const int&
	fun(int(30));//int&&
	return 0;
}

引用型别未定义,是模板的推演规则,不是右值引用,
fun传入什么类型的参数,t就是什么类型的引用

template<class _Ty>
my_remove_reference_t<_Ty>&& my_move(_Ty&& t) {
	return static_cast<my_remove_reference_t<_Ty>&&>(t);
}

int main() {
	MyString s1("yhping");
	const MyString s2("tulun");
	MyString s3, s4;
	s3 = my_move(s1);
	s4 = my_move(s2);
	return 0;
}

s3 = my_move(s1); s1是左值,所以t就是 MyString& 然后删除_Ty的引用型别,static_cast<my_remove_reference_t<_Ty>&&>(t); =》 static_cast<MyString&&>(t);
我们就将左值强转成了右值
s4 = my_move(s2); 转成常性右值,就只能调用深赋值函数,因为常性左值引用是万能引用

如果不用std::move,拷贝的代价很大,性能较低。使用move几乎没有任何代价,只是转换了资源的所有权。实际上是将左值变成右值引用,然后应用move语义调用移动赋值函数,就避免了拷贝,提高了程序性能。当一个对象内部有较大的堆内存或者动态数组时很有必要写move语义的拷贝构造函数和赋值函数,避免无谓的深拷贝,以提高性能。

这里也要注意对move语义的误解,move只是转移了资源的控制权,本质上是将左值强制转换为右值引用,以用于move语义,避免含有资源的对象发生无谓的拷贝。move对于拥有形如对内存、文件句柄等资源的成员的对象有效。如果是一些基本类型,比如int和char[10]数组等,如果使用move,仍然会发生拷贝(因为没有对应的移动构造函数),所以说move对于含资源的对象来说更有意义。

完美转发

void Print(int& a) { cout << "left value ref" << endl; }
void Print(const int& b) { cout << "const left value ref" << endl; }
void Print(int&& c) { cout << "right value ref" << endl; }
template<class _Ty>
void func(_Ty&& t) {
	Print(t);
}
int main() {
	int a = 10;
	const int b = 20;
	int&& c = 30;//
	func(a);
	func(b);
	func(c);
	func(40);
	return 0;
}

c本来是右值引用,但是具名后,变成左值,当将亡值一旦具名变成了左值,因此我们要求在程序的连续调用过程中,值的类型不可变,所以就有完美转发的概念
在这里插入图片描述

void func(_Ty&& t) {
	Print(forward<_Ty>(t));
}

在这里插入图片描述所谓完美转发(Perfect Forwarding),是指在函数模板中,不管参数是T&&这种未定的引用还是明确的左值引用或者右值引用,它会按照参数本来的值类型转发。

引用叠加

template<class _Ty>
struct remove_reference {
	using type = _Ty;
	using _Const_thru_ref_type = const _Ty;
};
template<class _Ty>
struct remove_reference<_Ty&> {
	using type = _Ty;
	using _Const_thru_ref_type = const _Ty&;
};
template<class _Ty>
struct remove_reference<_Ty&&> {
	using type = _Ty;
	using _Const_thru_ref_type = const _Ty&&;
};
template<class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;

template<class _Ty>
remove_reference_t<_Ty>&& move(_Ty&& _Arg)noexcept {
	return static_cast<remove_reference_t<_Ty> && (_Arg);
}

template<class _Ty>
_Ty&& my_forward(remove_reference_t<_Ty>& _Arg)noexcept {
	return static_cast<_Ty&&>(_Arg);
}
template<class _Ty>
_Ty&& my_forward(remove_reference_t<_Ty>&& _Arg)noexcept {
	return static_cast<_Ty&&>(_Arg);
}
void Print(int& a) { cout << "left value ref" << endl; }
void Print(const int& b) { cout << "const left value ref" << endl; }
void Print(int&& c) { cout << "right value ref" << endl; }
template<class _Ty>
void func(_Ty&& t) {
	Print(my_forward<_Ty>(t));
}
int main() {
	int a = 10;
	const int b = 20;
	int&& c = 30;
	func(a);
	func(b);
	func(c);
	func(40);
	return 0;
}
func(a);  _Arg =>  int&     _Ty =>  int&
_Ty&& my_forward(remove_reference_t<_Ty>& _Arg)noexcept   删除_Ty的引用型别
将t给_Arg  _Ty int& &&   左值引用叠加右值引用仍然是左值引用   最后返回_Ty&&  继续叠加,就是左值引用  最后返回左值引用,调用void Print(int& a)

func(40); , _Ty是整型 _Arg 也是int _Ty&& 就是右值引用 返回叠加也是右值引用 最后_Ty&& 最后将_Arg 强转成右值引用,调用void Print(int&& c)

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

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

相关文章

设计模式之责任链模式笔记

设计模式之责任链模式笔记 说明Chain of Responsibility(责任链)目录责任链模式示例类图请假条类抽象处理者类小组长类部门经理类总经理类测试类 说明 记录下学习设计模式-责任链模式的写法。JDK使用版本为1.8版本。 Chain of Responsibility(责任链) 意图:使多个对象都有机…

Vue搜索组件,显示热门、近期搜索(结合element ui)

&#x1f680; 注重版权&#xff0c;转载请注明原作者和原文链接 &#x1f96d; 作者&#xff1a;全栈小袁 &#x1f34e; 原创个人开源博客项目(目前V3.0版本)&#xff1a;https://github.com/yuanprogrammer/xiaoyuanboke &#x1f349; 开源项目觉得还行的话点点star&#x…

【P4】Windows 下搭建 DVWA 及命令注入漏洞详解

文章目录 一、Windows 下搭建 DVWA1.1、DVWA 靶场搭建1.2、六步快速搭建 DVWA1.2.1、下载并安装 PHPstudy&#xff1a;http://public.xp.cn/upgrades/PhpStudy2018.zip1.2.2、将解压后的 DVWA 原代码放置 phpstudy 安装目录的 WWW文件夹1.2.3、进入 DVWA/config 目录&#xff0…

2022前端趋势报告(下)

前端博主&#xff0c;热衷各种前端向的骚操作&#xff0c;经常想到哪就写到哪&#xff0c;如果有感兴趣的技术和前端效果可以留言&#xff5e;博主看到后会去代替大家踩坑的&#xff5e; 主页: oliver尹的主页 格言: 跌倒了爬起来就好&#xff5e; 一、前言 本文内容来自于《St…

springBoot学习——spring+springMVC 集成mybatis 拦截器

目录 引出入门案例&#xff1a;登陆和注册 & 用户信息分页 之 固定的步骤&#xff1a;&#xff08;1&#xff09;建普通项目配置pom.xml文件&#xff08;2&#xff09;写主启动类 application.yml文件【bug】pom.xml文件导了mybatis的包&#xff0c;但是application.yml文…

Drag Your GAN论文解读,基于点的交互式操作拖动到生成图像[DragGAN]

只需要鼠标的点击就可以自动修图的产品&#xff0c;火爆问世&#xff0c;可以说是超越PS&#xff0c;神一般的存在了&#xff0c;而且没有门槛&#xff0c;对于普通大众来说直接可以上手使用&#xff0c;这个是PS完全不具备的。更关键的是&#xff0c;这款产品跟PS明显区别在于…

如何系统地自学 Python?

&#x1f482; 个人网站:【海拥】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 目录 前言为什么选择Python作…

【Intel 黑客松大赛】基于YOLO的杂草-农作物检测分类系统

目录 一、赛题简介&#xff1a;计算机视觉挑战——检测并清除杂草二、基于YOLO的杂草-农作物检测分类2.1、YOLO简介2.2、基于YOLO的杂草-农作物检测分类解决方案 三、基于YOLO的杂草-农作物检测分类系统设计3.1、基于flask框架的demo应用程序后端3.2、基于Vue框架的demo应用程序…

Qt6.2教程——4.QT常用控件QPushButton

一&#xff0c;QPushButton简介 QPushButton是Qt框架中的一种基本控件&#xff0c;它是用户界面中最常见和最常用的控件之一。QPushButton提供了一个可点击的按钮&#xff0c;用户可以通过点击按钮来触发特定的应用程序操作。比如&#xff0c;你可能会在一个对话框中看到"…

Unity编辑器扩展-第七集-应用键/显示提示词

第六集链接&#xff1a;Unity编辑器扩展-第六集-创建窗口/批量填图_菌菌巧乐兹的博客-CSDN博客 一、本节目标效果展示 1.我们有时候需要多次使用编辑窗口&#xff0c;但是每次一点执行&#xff0c;就关掉了&#xff0c;就很烦&#xff0c;所以我们希望&#xff0c;点击按钮&…

Spring Bean的实例化过程

一、前言 对于写Java的程序员来说&#xff0c;Spring已经成为了目前最流行的第三方开源框架之一&#xff0c;在我们充分享受Spring IOC容器带来的红利的同时&#xff0c;我们也应该考虑一下Spring这个大工厂是如何将一个个的Bean生产出来的&#xff0c;本期我们就一起来讨论一…

2023年6月GESP能力等级认证Python一级真题

2023-06 GESP等级考Python一级真题 题数&#xff1a;27 分数&#xff1a;100 测试时长&#xff1a;90min 一、单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09; 1. 以下不属于计算机输出设备的有&#xff08; A&#xff09;。&#xff08;2分&#xff09; A…

几个Arcpy代码应用案例

1 案例一 删除特定记录 使用 arcpy 从 ArcGIS 中的图层中删除特定记录。可以使用更新游标&#xff08;UpdateSursor&#xff09;和 SQL 查询来识别和删除所需的记录。以下是如何删除特定图层记录的示例 import arcpy selectedParcelsselectedParcels expres…

分子碰撞频率和自由程------从一个物理小问题解剖自己的数学思维

物理学12-8节中&#xff0c;关于分子平均碰撞的解说如下&#xff1a; &#xff08;一&#xff09;分子碰撞自由程公式 第一个公式中&#xff0c;lamda v / z中&#xff0c;v的单位是m/s, z的单位是A/s, 其中A是常数。那么lamda描述的结果是m/A, 这正是长度单位。 此公式lamda…

利用正弦定理证明两角和差公式

首先用正弦定理&#xff0c;证明sin(AB)sinAcosBcosAsinB。 另外&#xff0c;其它的两角和差公式&#xff0c;都可以用三角函数奇偶性、诱导公式等推导出来&#xff0c;无需再用正弦定理证明一遍。

耗时一个月!手撸博客系统,主打美观实用!

先附上博客链接RoCBlog 关于博客 关于博客 RoCBlog 完成耗时&#xff1a;20天 起初是想搭建自己的博客&#xff0c;看了网上许多开源框架&#xff0c;感觉没啥意思&#xff0c;于是决定自己写一套 纯手撸VueSpringboot 其实是个我一边学vue一边写出来的东西&#xff0c;前期…

团体程序设计天梯赛-练习集L1篇⑩

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的普通人。 &#x1f3c0;个人主页&#xff1a;陈童学哦CSDN &#x1f4a1;所属专栏&#xff1a;PTA &#x1f381;希望各…

(贪心) 1221. 分割平衡字符串 ——【Leetcode每日一题】

❓ 1221. 分割平衡字符串 难度&#xff1a;简单 平衡字符串 中&#xff0c;L 和 R 字符的数量是相同的。 给你一个平衡字符串 s&#xff0c;请你将它分割成尽可能多的子字符串&#xff0c;并满足&#xff1a; 每个子字符串都是平衡字符串。 返回可以通过分割得到的平衡字符…

【JAVA反序列化】序列化与反序列化Java反射URLDNS链

文章目录 原生序列化与反序列化概述为什么需要序列化和反序列化&#xff1f;应用场景(涉及到将对象转换成二进制&#xff0c;序列化保证了能够成功读取到保存的对象)涉及的协议好处为什么会产生反序列化漏洞&#xff1f;可能反序列化的形式&#xff1f;代码演示 Java反射基础补…

Java try-catch块

Java的try块用于封装可能会抛出异常的代码。它必须在方法内部使用。 如果在try块中的特定语句处发生异常&#xff0c;后续的代码块将不会执行。因此&#xff0c;建议不要在try块中放置不会抛出异常的代码。 Java的try块必须后跟catch块或finally块。 Java try-catch语法 try…