右值引用和移动语义

news2025/9/20 11:00:37

什么是左值?什么是右值?

通俗来讲,可以出现在赋值语句左侧的,为左值;只能出现在赋值语句右侧的,为右值。

左值与右值的本质区别在于:左值能取地址,但右值不能

本文主要通过三个场景 —— 与 移动构造、移动赋值、完美转发 有关,讲解右值引用在实际场景中的作用及其相关的知识。

补充: 右值通常也被形象的称为“将亡值”,代指赋值重载和拷贝构造过程中产生的临时对象

为了观察现象,我们要用到“自己搭的轮子”:

namespace MyTest
{
	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(const char* str)" << endl;

			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		// s1.swap(s2)
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造 -- 左值
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

		// 拷贝赋值
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 拷贝赋值" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		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';
		}


		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0; // 不包含最后做标识的\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;
	}
}

1. 移动构造

1.1 原理讲解
int main()
{
    MyTest::string s1 = MyTest::to_string(1234);
    
    return 0;
}

要分别从 C++11 前后编译器优化前后 四个维度讨论 MyTest::string s1 = MyTest::to_string(1234); 的发生过程。

观察上图的右下角:很明显,str 是左值,在没有 move 的情况下,为什么可以直接移动构造

实际上,编译器已经做过优化了 —— 将传值返回函数的返回值隐式地 move 过了

1.2 移动构造实现
	// 移动构造
    string(string&& s)
    {
        cout << "string(string&& s) -- 移动构造" << endl;
        swap(s);
    }

2. 移动赋值

2.1 原理
int main()
{
	MyTest::string s1;
	s1 = MyTest::to_string(1234);

	return 0;
}

2.2 移动赋值实现
	// 移动赋值
    string& operator=(string&& s)
    {
        cout << "string& operator=(string&& s) -- 移动赋值" << endl;
        swap(s);

        return *this;
    }

3. 完美转发

3.1 forward 及其用法

move(...) 表达式的结果为右值,但并不会改变 ... 本身的属性 。

forward<T>(x) 返回一个引用表达式,这个引用表达式的类型取决于 x 的原始类型和上下文。

​ 若 x 为右值,该引用表达式具有右值属性

​ 若 x 为左值,该引用表达式具有左值属性

  • 右值的右值引用具有左值属性
void func(const int& x)
{
	cout << "const int&" << endl;
} 

void func(int&& x)
{
	cout << "int&&" << endl;
}

template<class T>
void fun(T&& x)
{
	func(x);// 此处 x 为右值还是左值呢?不妨运行该段代码,试试看
}

int main()
{
	// int&& a = 10;// 10 为右值;a 为 10 的右值引用,因此具有左值属性
	fun(10);

	return 0;
}

做一处修改:

template<class T>
void fun(T&& x)
{
	func(forward<T>(x));// 再运行试试
}

3.2 模板中的 && 万能引用

请记住:万能引用 与 模板 紧密相关!

void func(int& x) { cout << "左值引用" << endl; }
void func(const int& x) { cout << "const 左值引用" << endl; }
void func(int&& x) { cout << "右值引用" << endl; }
void func(const int&& x) { cout << "const 右值引用" << endl; }

template<class T>
void Universal_citation(T&& x) // 万能引用
{ 
    func(forward<T>(x));
}

int main()
{
    int a;
    Universal_citation(a);
    Universal_citation(move(a));
    
    const int b = 10; // b 必须要初始化
    Universal_citation(b);
    Universal_citation(move(b));
    
    return 0;
}

3.3 实际场景

再引入一个“自己造的轮子” —— list :

namespace MyTest
{
	template<class T>
	struct ListNode
	{
		ListNode<T>* _next;
		ListNode<T>* _prev;
		T _data;

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

	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef ListNode<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;
		Node* _node;

		__list_iterator(Node* x)
			:_node(x)
		{}

		// ++it
		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		// it++
		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;
		}

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

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

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

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

	template<class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		typedef __list_iterator<T, T&, T*> iterator;

		list()
		{
			_head = new Node(T());
			_head->_prev = _head;
			_head->_next = _head;
		}

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

		iterator end()
		{
			return _head;
		}

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

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

		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* newNode = new Node(x);
            
			// prev newNode cur
			Node* prev = cur->_prev;
			prev->_next = newNode;
			newNode->_prev = prev;
			newNode->_next = cur;
			cur->_prev = newNode;
			
			return newNode;
		}

	private:
		Node* _head = nullptr;
	};
}

push_back() 加上右值版本:

template<class T>
struct ListNode
{
    ListNode(T&& x)
    	:_next(nullptr)
        ,_prev(nullptr)
        ,_data(forward<T>(x))// 完美转发
    {}
};

template<class T>
class list
{
	void push_back(T&& x)
    {
        insert(end(), forward<T>(x));// 关键处,完美转发
    }

    iterator insert(iterator pos, T&& x)
    {
        Node* cur = pos._node;
        Node* newNode = new Node(forward<T>(x));// 完美转发

        // prev newNode cur
        Node* prev = cur->_prev;
        prev->_next = newNode;
        newNode->_prev = prev;
        newNode->_next = cur;
        cur->_prev = newNode;

        return newNode;
    }
};

4. 针对 移动构造 和 移动赋值重载

  • 如果自己没有实现 移动构造移动赋值重载,且没有实现析构函数、拷贝构造、赋值重载的任意一个,编译器会自动生成默认的移动构造或移动赋值重载。

​ 内置类型,完成浅拷贝;自定义类型,调用实现的移动构造(没实现移动构造,则调用拷贝构造)。

  • 如果提供了移动构造或移动赋值,则编译器不会自动生成拷贝构造和拷贝赋值

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

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

相关文章

Commons-io工具包与Hutool工具包

Commons-io Commons-io是apache开源基金组织提供的一组有关IO操作的开源工具包 作用:提高I0流的开发效率。 FileUtils类(文件/文件夹相关) static void copyFile(File srcFile,File destFile) 复制文件 static void copyDirectory(File srcDir,File destDir) 复…

你的职业规划就是面向贫穷的规划

如果你觉得作者的文章还有点用,请记得点赞 + 关注 说一个扎心的事实,就是我们绝大多数人的职业规划基本上都是错误的,都是面向贫穷的规划。 因为绝大多数人的职业规划都是打工人的职业规划,这种规划除了很少部分人最终能成为企业高管,实现层级跃迁外,绝大多数人在大多数…

递归解析 LXML 树并避免重复进入某个节点

1、问题背景 我们在使用 LXML 库解析 MathML 表达式时&#xff0c;可能会遇到这样一个问题&#xff1a;在递归解析过程中&#xff0c;我们可能会重复进入同一个节点&#xff0c;导致解析结果不正确。例如&#xff0c;我们希望将以下 MathML 表达式解析为 Python 表达式&#x…

DJ-122A电压继电器 柜内安装 电磁式继电器 约瑟JOSEF

系列型号&#xff1a; DJ-100A/Q系列电压继电器 DJ-111A/Q电压继电器; DJ-112A/Q电压继电器; DJ-121A/Q电压继电器; DJ-122A/Q电压继电器; DJ-131A/Q电压继电器; DJ-132A/Q电压继电器; DJ-131A/Q160CN电压继电器; DJ-131A/Q160C电压继电器; ​ 概述 DJ-100A/Q系列电…

linux安装dgl

1.DGL官网、选择与自己cuda、python版本匹配的dgl的whl文件CUDA11.8、python10并下载 2.用pip install运行 pip install /home/u2023170749/download/dgl-2.2.0cu118-cp310-cp310-manylinux1_x86_64.whl

RocketMQ在Centos7系统上单机部署

最近因为一些信创问题&#xff0c;要将RabbitMQ替换为RocketMQ&#xff0c;因此在此分享一些RocketMQ在Centos7系统上单机部署相关过程。 优缺点 RocketMQ的优点&#xff1a; 性能优越&#xff1a;RocketMQ在处理大量消息时&#xff0c;性能优于RabbitMQ。当面临每秒数万到数…

【云岚到家】-day04-2-索引同步-搜索接口

【云岚到家】-day04-2-索引同步-搜索接口 1 索引同步1.1 编写同步程序1.1.1 创建索引结构1.1.2 编写同步程序1.1.2.1 添加依赖1.1.2.2 配置连接ES1.1.2.3 编写同步程序 1.1.3 测试1.1.4 小结1.1.4.1 如何保证CanalMQ同步消息的顺序性&#xff1f;1.1.4.2 如何保证只有一个消费者…

抖店一件代发设置自动发货

工具&#xff1a;逸淘 山东逸淘软件-www.1tsoft.com-一键下单&#xff01; step1 下载插件 下载对应浏览器的插件 step2 插件安装 谷歌浏览器--右上角...--设置--扩展程序 把下载的插件从文件夹拖过来 备注&#xff1a;显示 错误 不影响使用 在浏览器网址右边--把插件固定…

香港优才自测140分,带36万+申请香港优才身份被拒了

最近又有一个朋友跟我诉说&#xff0c;一家人申请优才&#xff0c;带几十万yi民香港&#xff0c;还被拒了&#xff0c;真的好气&#xff01; 费心费力等一年以上才收到拒批通知了&#xff0c;被拒的朋友有因为资料准备不足的&#xff0c;有因为错误自测分数给入境处过高期望的&…

【数据结构初阶】--- 栈和队列

栈 栈的定义 栈&#xff1a;只允许在一端进行插入或删除的操作 事实上&#xff0c;线性表和链表都可以实现栈&#xff0c;但栈的特点更符合用顺序表实现 顺序表的队尾相当于栈顶&#xff0c;对栈放入数据&#xff0c;相当于顺序表的下标arr[index] x&#xff0c;而栈弹出数…

老爷机带不动影视后期?云桌面了解一下

从黑白到彩色&#xff0c;从默片到有声&#xff0c;从2D到3D&#xff0c;影视工业经过百余年的演变&#xff0c;每一步技术的提升都试图让影视艺术更接近“真实”。电影特效技术的诞生&#xff0c;更是为影视作品的真实感实现了一次巨大的飞跃。 但有一部分影视人&#xff0c;…

毕业年薪30W起!25届最近5年浙江大学自动化考研院校分析

浙江大学 目录 一、学校学院专业简介 二、考试科目指定教材 三、近5年考研分数情况 四、近5年招生录取情况 五、最新一年分数段图表 六、历年真题PDF 七、初试大纲复试大纲 八、学费&奖学金&就业方向 一、学校学院专业简介 二、考试科目指定教材 1、考试科目…

流程与IT双驱动:锐捷网络如何构建持续领先的服务竞争力?

AI大模型及相关应用进入“竞赛时代”&#xff0c;算力作为关键要素备受关注&#xff0c;由于算力行业对网络设备和性能有较大需求&#xff0c;其发展也在推动ICT解决方案提供商加速升级&#xff0c;提升服务响应速度和服务质量。 锐捷网络是行业领先的ICT基础设施及行业解决方…

黄河流域web

1、UNSER的 <?php highlight_file(__FILE__); class Wel {public $fast;public $star;public function __construct(){$this->fast "free_toto";echo "what?";}public function __destruct(){$content $this->star;printf ($content);}pu…

Prometheus写入influxDB:中间件remote_storage_adapter

Prometheus写入influxDB&#xff1a;中间件remote_storage_adapter prometheus默认采用的是本地磁盘做数据存储&#xff0c;本地存储的优势就是运维简单但是缺点就是无法海量的metrics持久化和数据存在丢失的风险,数据写入可能造成wal文件损坏导致采集数据无法再写入的问题。 …

5.4.Python 装饰器与语法糖

1. 装饰器 1.1 装饰器简介 装饰器的作用: 对函数进行装饰, 添加新的功能. 装饰器的原则: 开发封闭原则, 对扩展开放, 对修改封闭. 封闭: 已现实的功能代码块不应该被修改. 开放: 对现有功能的拓展开放. 在不改变被装饰对象内部代码以及调用方式的基础上为函数添加新的功能.1.…

Java课程设计:基于swing的学生信息管理系统

文章目录 一、项目介绍二、项目展示三、源码展示四、源码获取 一、项目介绍 这款Java swing实现的学生信息管理系统和jsp版本的功能很相似&#xff0c;简单的实现了班级信息的增删改查&#xff0c;学生信息的增删改查&#xff0c;数据库采用的是mysql&#xff0c;jdk版本不限&…

fuel无人机自主探索代码解读3——fast_exploration_fsm.cpp【状态机】

一、概述 fast_exploration_fsm.cpp订阅实时定位和目标点信息&#xff0c;每隔0.01s执行一次状态机&#xff0c;进行状态切换&#xff1b;每隔0.05s执行一次碰撞检测&#xff0c;按需进行重新规划&#xff1b;每0.5s执行一次边界回调定时器&#xff0c;对于处于WAIT_TRIGGER和…

nacos注册中心配置中心

文章目录 1.Nacos安装与简单使用1.1. Nacos安装指南Windows安装下载安装包解压端口配置启动访问 Linux安装安装JDK上传安装包解压端口配置启动 1.2.服务注册到nacos使用步骤引入依赖配置nacos地址重启 示例父工程pom.xmluser-servicepom.xmlapplication.ymlUserApplicationUser…

杨校老师项目之基于SpringBoot的理发店的预约管理系统

原系统是SSMJSP页面构成&#xff0c;先被修改为SpringBoot JSP页面 自助下载渠道: https://download.csdn.net/download/kese7952/89417001&#xff0c;或 点我下载 理发师信息&#xff1a; 理发师详细信息 公告信息 员工登录&#xff1a; 管理员登录