C++ 右值引用 std::move和std::forward的使用

news2025/1/20 1:35:39

前言

右值引用,std::move(移动语义)和std::forward(完美转发)都是C++11里面的特性。
使用右值引用和移动语义,可以避免无谓的复制,提供了程序性能。

右值引用

在说明右值引用之前,先说下什么是左值,什么是右值。 左值是表达式结束后仍然存在的持久对象,右值是指表达式结束时就不存在的临时对象。
区分左值和右值的便捷方法是看能不能对表达式取地址,如果能则为左值,否则为右值;
将亡值是C++11新增的、与右值引用相关的表达式,比如:将要被移动的对象、T&&函数返回的值、std::move返回值和转换成T&&的类型的转换函数返回值。
C++11中的所有的值必将属于左值、将亡值、纯右值三者之一,将亡值和纯右值都属于右值。

&&的作用

右值引用就是对一个右值进行引用的类型。因为右值没有名字,所以我们只能通过引用的方式找到它。无论声明左值引用还是右值引用都必须立即进行初始化,因为引用类型本身并不拥有所把绑定对象的内
存,只是该对象的一个别名。
通过右值引用的声明,该右值又“重获新生”,其生命周期其生命周期与右值引用类型变量的生命周期一样,只要该变量还活着,该右值临时量将会一直存活下去。
在这里先提一下,具有T&&val,不一定是右值引用,也可能是左值引用,具体要看val是什么值,如果val是一个左值,那么就是一个左值引用,如果是一个右值,那就是一个右值引用。这点非常让人迷糊,后面使用代码在进行说明,这里先记一下

move语义

我们知道移动语义是通过右值引用来匹配临时值的,那么,普通的左值是否也能借组移动语义来优化性能呢?C++11为了解决这个问题,提供了std::move()方法来将左值转换为右值,从而方便应用移动语义。move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转义,没有内存拷贝。
在这里插入图片描述

移动构造和拷贝构造

下面以类的拷贝构造和移动构造函数来说明右值引用和std::move的作用

#include <iostream>
#include <string>
using namespace std;
class MyString {
public:
	MyString() {
		std::cout << "无参构造函数" << std::endl;
	}
	MyString(const string& ptr) :m_ptr(new string(ptr)) {
		std::cout << "有参构造函数" << std::endl;
	}
	//由于是指针,拷贝的时候需要做深拷贝,如果做浅拷贝的话,会释放2次,会导致异常发生
	MyString(const MyString& m_str) {
		if (m_str.m_ptr) {//做一个非空判断,保证安全
			m_ptr = new string(*m_str.m_ptr);
		}
		std::cout << "拷贝构造函数" << std::endl;
	}

	MyString& operator=(const MyString& other) {
		if (&other != this)//如果不是自生,做赋值操作
		{
			if (m_ptr) { //防止赋值之前已经存在资源,如果不释放就会造成内存泄漏
				delete m_ptr;
				std::cout << "copy operator= delete" << std::endl;
			}
			m_ptr = nullptr;
			if (other.m_ptr) //如果传递的为nullptr,
			{
				m_ptr = new string(*other.m_ptr);//也可能出现问题,这里不考虑,假如new失败了,m_ptr就不能返回之前的状态了。
			}
			std::cout << "拷贝赋值函数" << std::endl;
		}
		return *this;
	}
	//与拷贝构造的区别,参数不能使用const,因为参数会进行移动
	//使用的是浅拷贝,直接把之前的资源拿过来,而不是去做真正的内存开辟操作
	//使用 noexcept 修饰 是为了提供强异常保证,
	//即在移动过程中即使发生异常(资源也能够恢复为之前的状态),也能保证程序的正确性。
	MyString(MyString&& m_str) noexcept :m_ptr(m_str.m_ptr) {
		m_str.m_ptr = nullptr;//移动资源过后,设置为nullptr,不然会导致多次调用析构的delete
		std::cout << "移动构造函数" << std::endl;
	}

	MyString& operator=(MyString&& other) noexcept {
		if (&other != this)//如果不是自己,做赋值操作
		{
			if (m_ptr) { //防止赋值之前已经存在资源,如果不释放就会造成内存泄漏
				delete m_ptr;
				std::cout << "move operator= delete" << std::endl;
			}
			m_ptr = other.m_ptr;
			other.m_ptr = nullptr;//移动资源过后,设置为nullptr,不然会导致多次调用析构的delete
			std::cout << "移动赋值函数" << std::endl;
		}
		return *this;
	}

	~MyString() {
		if (m_ptr)
		{
			std::cout << "delete ptr:" << m_ptr << std::endl;
			delete m_ptr;
		}
		m_ptr = nullptr;
		std::cout << "~Mystring" << std::endl;
	}
private:
	std::string* m_ptr = nullptr;//提前设置为nullptr,防止指针生成一个随机值,释放一个无效指针报错
};
int main{
	{
	MyString a,b;//调用2次无参构造函数
	
	//先调用有参构造构造出MyString("Hello")临时对象--->会分配一次内存
	//由于MyString("Hello") 返回的对象是一个将亡值(右值),随后通过这个右值赋值给a后调用移动赋值
	//赋值完成之后,MyString("Hello")临时对象生命周期也结束了,自然会调用析构函数,
	//但是里面的new出来的指针对象会移动到a对象中,因此指针的内容是不会被释放的。
	a = MyString("Hello");//有参构造->移动赋值->析构函数(不会释放资源)
	
	//由于a是一个左值,不会有任何释放操作,仅仅调用拷贝构造函数(会分配内存)a和d都需要释放内存
	MyString d = a;//拷贝构造
	b = std::move(a);//移动赋值
	b = a; //拷贝赋值函数
}

在上面的代码里,构造了一个MyString类,里面有一个string类型的指针,同时这个类具有构造,析构,拷贝构造,拷贝赋值,移动构造,移动赋值函数。
在使用的时候,如果使用了移动赋值和移动构造,不会new对象,生成一个新的对象,而是把别人的资源给抢占过来,供自己使用,但是如果使用拷贝构造和拷贝赋值的时候,会为string类型的指针开辟一个空间,然后将内容复制过来。也就是深拷贝在使用std::move的时候可以将左值a变为右值,从而节约了拷贝的开销,这在类中具有大数据结构的时候是非常有必要的。因此右值和move语义在开发中是不可缺少的

forward 完美转发

在说明完美转发之前,我们先用一段测试代码来说明其作用。

#include <iostream>

template<class T>
void print(T& t)
{
	std::cout << "L:" << t << std::endl;
}
template<class T>
void print(T&& t)
{
	std::cout << "R:" << t << std::endl;
}

/// <summary>
/// 注意,&&引用类型即可以时左值引用,也可以是右值引用
/// 引用之后,val值就变成了一个左值(需要注意)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="val"></param>
template<class T>
void func(T&& val)
{
	print(val);//左
	print(std::move(val));//右
	print(std::forward<T>(val));//保持val的左值或者右值属性
}
int main {
	//a是一个右值引用,但其本身a也有内存名字,所以a变成了一个左值
	int&& a = 10;//虽然使用了&&,不要误认为a就是右值了,a这时候是一个左值了
	//int&& b = a;//由于a是左值,不能使用右值引用去引用左值了。
	int& c = a;//a是左值,可以使用左值引用去引用。
	std::cout << "func(1)" << std::endl;
	func(1);//1是右值,执行fun过后之后,变成:左,右,右
	int x = 10;
	int y = 20;
	std::cout << "func(x)" << std::endl;
	func(x);//x是左值,执行fun过后之后,变成:左,右,左
	std::cout << "func(std::forward<int>y)" << std::endl;
	func(std::forward<int>(y));//std::forward<int>(y)会将左值转换为右值,执行fun过后之后,变成:左,右,右
}

当传递右值1的时候,func(1)的输出为左,右,右,这就很奇怪了,明明传递了一个右值 ,为啥经过模板函数template func(T && val),val就变成了左值?,是否有什么办法完美转发这个属性呢?
对于下面这个模板函数

Template<class T> void func(T &&val);

前面有提到过,对于 &&类型,既可以是一个左值引用,也可以是一个右值引用,具体要看val是什么值,在这里val其实是一个左值,而不是一个右值,即使我们传递了一个1。 对于下面代码:

int &&a = 10;
int &&b = a; //错误,a是一个左值,不能用一个右值引用去存储

a是一个右值引用,但其本身a也有内存名字,所以a本身是一个左值,再用右值引用引用a这
是不对的。

那么有什么办法可以让这个1识别为一个右值呢,那就是使用std::forward完美转发,调用func(1),然后在调用print(std::forward(val)),就知道std::forward完美转发了其属性(如果传递的是右值,那么就会保持右值得属性,如果传递得是左值,就会保持左值的属性)。
在main函数里func(std::forward(y));这里y是一个左值,但是使用std::forward(y)变成了右值,这里需要注意以下,这时候std::forward和std::move一样了。

总结

右值引用和move语义结合起来,能够很好的实现移动语义,具体的move语义将一个左值变成一个右值,右值引用实现了移动语义,完美转发std::forward在函数调用时,能够保持变量的本来属性。但是std::forward在同一个函数调用时,会将左值变成一个右值,这跟std::move是一样的。

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

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

相关文章

Stable Diffusion与Midjourney:如何做出明智之选?

Stable Diffusion与Midjourney&#xff1a;如何做出明智之选&#xff1f; 在人工智能领域中&#xff0c;Stable Diffusion和Midjourney是两个备受瞩目的技术。它们各自具有独特的特点和优势&#xff0c;但选择哪一个更适合您的需求呢&#xff1f;本文将为您详细分析两者的差异…

Unity中创建Ultraleap 3Di交互项目

首先&#xff0c;创建新的场景 1、创建一个空物体&#xff0c;重命名为【XP Leap Provider Manager】&#xff0c;并在这个空物体上添加【XR Leap Provider Manager】 在物体XP Leap Provider Manager下&#xff0c;创建两个子物体Service Provider(XR)和Service Provider(…

蓝桥杯省赛无忧 课件46 第5次学长带练配套课件

01 聪明的小羊肖恩 02 阿坤老师的独特瓷器 03 妮妮的月饼工厂 04 可凑成的最大花束数 05 四个瓶子的问题 06 如今扔是遥远的理想之城

12.5内存操作流(血干JAVA系列)

12.5内存操作流 12.5内存操作流ByteArraylnputStream类的主要方法ByteArrayOutputStream 类的主要方法【例12.33】使用内存操作流完成一个大写字母转换为小写字母的程序 12.5内存操作流 在 流 的 操 作 中 除 了 进 行 文 件 的 输 入 与 输 出 操 作 之 外 &#xff0c; 也 可…

Modern C++ std::unique_ptr的实现原理

​ unique_ptr是一个非常简单的类,没有计数没有原子操作,非常类似纯指针。 它的类定义也非常简单: 它针对数组做了模板偏特化,因为它得支持数组操作比如Arr[i]。 unique_ptr的本质就是std::tuple, 里面第一项为指针指向管理对象,第二项为deleter:是一个函数指针或仿函数…

什么是网络?

你是一台电脑&#xff0c;你的名字叫 A 很久很久之前&#xff0c;你不与任何其他电脑相连接&#xff0c;孤苦伶仃。 直到有一天&#xff0c;你希望与另一台电脑 B 建立通信&#xff0c;于是你们各开了一个网口&#xff0c;用一根网线连接了起来。 用一根网线连接起来怎么就能&…

探索如何使用Python实现关注微信公众号实现登录的功能(用户认证)

文章目录 📖 介绍 📖🏡 环境 🏡📒 实现方法 📒📝 准备工作📝 源码分享⚓️ 相关链接 ⚓️📖 介绍 📖 本文将与大家分享一下如何使用python实现扫描二维码关注微信公众号,并通过用户认证从而实现登录。本文将主要分享功能实现的完整思路,并包含部分功能实…

使用人工智能助手 Github Copilot 进行编程 02

本章涵盖了 在您的系统上设置 Python、VS Code 和 Copilot引⼊ Copilot 设计流程Copilot 的价值在于基本的数据处理任务本章将帮助您在自己的计算机上开始使用 Copilot,并熟悉与其的交互方式。在设置好Copilot 后,我们将要求您尽可能跟随我们的示例进行操作。实践是最好的学习…

数据结构C++队列(数组模拟)

队列也是比较简单的数据结构了&#xff0c;队列的特点是先进先出 下面代码中hh是队头&#xff0c;tt是队尾。 默认是从队尾插入数据&#xff0c;队头弹出数据。 代码中的数据结构可以使用这图片来解释&#xff0c;整个区间是数组q。hh和tt分别控制队头和队尾。 例题&#x…

cocos添加节点事件的3种方式

我们以button为例来说明一下cocos怎样为节点添加事件&#xff1a; 直接通过cocos熟悉检查器绑定 添加事件脚本 import { _decorator, Component, Node, input, Input, Button, EventKeyboard } from cc; const { ccclass, property } _decorator;ccclass(Attack) export cla…

【笔试常见编程题01】删除公共字符串、组队竞赛、倒置字符串、排序子序列

1. 删除公共字符串 输入两个字符串&#xff0c;从第一字符串中删除第二个字符串中所有的字符。 例如&#xff0c;输入”They are students.”和”aeiou”&#xff0c;则删除之后的第一个字符串变成”Thy r stdnts.” 输入描述 每个测试输入包含2个字符串 输出描述 输出删除后的…

事务:分布式事务与本地事务的区别

分布式事务章节 分布式事务&#xff1a;2PC与3PC的区别-CSDN博客 分布式事务&#xff1a;X/Open DTP分布式事务处理模型与分布式事务处理XA规范-CSDN博客 事务简介 事务(Transaction)是操作数据库中某个数据项的一个程序执行单元(unit)。事务是由一组操作构成的可靠的独立的…

【Linux】Linux中的日志查询方法

文章目录 linux日志与日志的查询方法更多journalctl用法journalctl用法案例部分日志路径说明推荐阅读 linux日志与日志的查询方法 在Linux系统中&#xff0c;日志文件用于记录系统的各种运行信息和错误消息。常见的日志文件包括但不限于/var/log/下的各种日志&#xff0c;如me…

foxmail开启不能自动启动解决办法

1、不要以管理员权限启动&#xff0c;不勾选管理员权限。 2、在foxmail设置里勾选“开机自动启动”

微信小程序(二十)Vant组件库的配置

教程很详细&#xff0c;直接上过程 上一篇 官方文档也有&#xff0c;但是因为版本的更新&#xff0c;官方文档并没有跟着改变&#xff0c;这里我写一份最新版能用的教程 &#xff08;口头禅还是不能少的&#x1f923;&#x1f923;&#x1f923;&#xff09; 灵魂拷问&#xf…

增加/调整硬盘空间有效的 8 款管理磁盘分区软件分享

适用于电脑的最佳分区管理器是一个出色的工具&#xff0c;可以增加硬盘空间并确保最有效地管理磁盘分区。因此&#xff0c;请继续阅读以详细了解它们。 电脑上的存储问题并不是什么新鲜事。然而&#xff0c;随着最新 电脑的出现&#xff0c;情况有所改善。但似乎没有足够的空间…

数据监控-Prometheus/Grafana

一、数据监控Prometheus 1、什么是Prometheus Prometheus是由SoundCloud开源监控告警解决方案,从2012年开始编写代码,到2015年github上开源以来,吸引不少用户以及公司的使用。Prometheus作为新一代的开源解决方案,很多理念与Google SRE的运维之道不谋而合。 2、Promet…

如何使用数据恢复软件恢复已删除的文件

在计算机时代之前&#xff0c;数据一直以物理方式存储在纸张上。然后通过收集论文&#xff0c;创建了一个大数据库。收集到的论文存放在大房间或数据收集中心。笔和纸系统的一个很大的缺点是&#xff0c;如果你想找到特定的数据&#xff0c;就像在沙子里找到一根针一样。 计算…

2023 IoTDB Summit:Dr. Feinauer《Apache IoTDB在德国工业和关键基础设施中的应用》

12 月 3 日&#xff0c;2023 IoTDB 用户大会在北京成功举行&#xff0c;收获强烈反响。本次峰会汇集了超 20 位大咖嘉宾带来工业互联网行业、技术、应用方向的精彩议题&#xff0c;多位学术泰斗、企业代表、开发者&#xff0c;深度分享了工业物联网时序数据库 IoTDB 的技术创新…

幻兽帕鲁的搭建和幻兽帕鲁需要什么配置的服务器

前言 大家好&#xff0c;今天教大家如何快速搭建幻兽帕鲁&#xff0c;并能满足8-32人游玩 第一步 购买服务器 1.CPU&#xff1a;4核&#xff08;最低需要4核起&#xff0c;当然可以选择更高的&#xff09;CPU的选择更看重单核性能&#xff0c;尽量选择主频2.5GHz以上的&#…