左值引用与右值引用

左值和右值

左值(lvalue):在表达式结束后仍然存在,可以取地址。简单理解:有名字、有存储位置。
比如变量、数组元素、对象等。

右值(rvalue):临时值,表达式结束后就消失,不能取地址。
比如字面量、表达式的临时结果。

int x = 10;      // x 是左值
int y = x;       // x 是左值,10 是右值
int z = x + y;   // (x + y) 是右值,z 是左值

左值引用

左值引用就是对 左值的引用。
语法:T& ref = var;

int a = 5;
int& ref = a;  // ref 是 a 的别名
ref = 10;      // 改变 ref 其实就是改变 a

⚠️ 注意:左值引用不能直接绑定到右值上。

int& r = 5;   // ❌ 错误,5 是右值

右值引用

右值引用是 C++11 引入的,允许绑定到 右值。
语法:T&& ref = expr;

int&& r = 5;       // ✅ r 引用了一个临时右值
int&& r2 = a + 3;  // ✅ a + 3 是右值

右值引用的意义:

可以延长临时值的生命周期。

用于 移动语义(move semantics) 和 完美转发(perfect forwarding)。

左值引用 vs 右值引用的函数参数示例

void f(int& x) {        // 接受左值std::cout << "左值引用\n";
}
void f(int&& x) {       // 接受右值std::cout << "右值引用\n";
}int main() {int a = 10;f(a);     // 传左值,调用 f(int&)f(20);    // 传右值,调用 f(int&&)
}
输出:左值引用
右值引用

应用场景:移动语义

右值引用最重要的用途是避免不必要的拷贝,提高效率。

#include <iostream>
#include <vector>
using namespace std;int main() {vector<int> v1 = {1,2,3,4,5};vector<int> v2 = std::move(v1); // 使用右值引用转移资源cout << "v1 size: " << v1.size() << endl;cout << "v2 size: " << v2.size() << endl;
}
结果:v1 size: 0
v2 size: 5

这里 std::move 把 v1 转换成右值引用,避免了数据拷贝,而是直接转移所有权。

移动构造时发生了什么?

以 std::vector 为例,里面有三个关键成员:

指向堆区的指针 ptr

当前元素个数 size

容量 capacity

拷贝构造

会重新分配一块堆内存,把 v1 的数据拷贝过去。

移动构造

直接把 v1 的内部指针 ptr 交给 v2。

然后把 v1 的 ptr 置空,size 和 capacity 设为 0。

所以:

v1 变成一个 空的 vector(但依然是合法对象)。

v2 拥有了原来 v1 的那块内存。

v2 用完之后,内存会销毁吗?

是的。
当 v2 生命周期结束时,它会调用析构函数,释放那块堆内存。

而 v1 在 std::move 之后,它的 ptr 已经被清空,所以它的析构函数不会释放任何东西(避免二次释放)。

直观理解(类比搬家 🚚)

v1 原来有一套家具(堆内存)。

std::move(v1) 把家具的所有权交给了 v2。

v1 自己变成了一个空房子(里面啥都没有,但房子还在)。

当 v2 销毁时,它负责把家具丢掉。

示例

  1. 问题:没有移动语义时的性能问题
    假设我们有一个BigData类,它内部持有一个很大的动态数组(这里用std::vector模拟)。拷贝这个类的对象会非常昂贵,因为它需要分配新的内存并复制所有数据。
#include <iostream>
#include <vector>
#include <chrono>class BigData {
private:std::vector<int> m_data; // 模拟一个很大的数据块public:// 构造函数,分配大量数据BigData(size_t size) : m_data(size) {std::cout << "Constructor called. Allocated " << size << " elements." << std::endl;}// 拷贝构造函数(深拷贝)- 性能瓶颈!BigData(const BigData& other) : m_data(other.m_data) {std::cout << "Copy Constructor called. Expensive deep copy!" << std::endl;}// 拷贝赋值运算符(深拷贝)BigData& operator=(const BigData& other) {if (this != &other) {m_data = other.m_data; // 又一次昂贵的拷贝!}std::cout << "Copy Assignment called. Expensive deep copy!" << std::endl;return *this;}// 析构函数~BigData() {std::cout << "Destructor called." << std::endl;}
};// 一个函数,返回一个BigData对象
BigData createBigData() {BigData data(1000000); // 在函数内部创建一个大对象return data; // 传统C++03中,这里可能会触发拷贝
}int main() {BigData my_data = createBigData(); // 这里期望得到函数内部创建的对象return 0;
}
  1. 解决方案:实现移动语义
    移动语义允许我们“窃取”即将被销毁的对象的资源,而不是进行昂贵的拷贝。我们通过定义移动构造函数和移动赋值运算符来实现这一点。
#include <iostream>
#include <vector>
#include <utility> // for std::moveclass BigData {
private:std::vector<int> m_data;public:BigData(size_t size) : m_data(size) {std::cout << "Constructor called. Allocated " << size << " elements." << std::endl;}// 1. 移动构造函数 (参数是非常量右值引用 BigData&&)BigData(BigData&& other) noexcept // noexcept 很重要,用于标准库优化: m_data(std::move(other.m_data)) // 关键:使用std::move移动内部的vector{std::cout << "Move Constructor called. Efficient move!" << std::endl;// 移动后,源对象‘other’的m_data现在处于有效但未状态(通常是空)}// 2. 移动赋值运算符BigData& operator=(BigData&& other) noexcept {if (this != &other) {m_data = std::move(other.m_data); // 关键:移动赋值}std::cout << "Move Assignment called. Efficient move!" << std::endl;return *this;}// 保留拷贝构造和拷贝赋值,实现“Rule of Five”BigData(const BigData& other) = default;BigData& operator=(const BigData& other) = default;~BigData() = default;
};// 工厂函数
BigData createBigData() {BigData data(1000000);return data; // 编译器意识到‘data’是局部对象,是“将亡值”// 优先选择移动构造函数,即使没有移动构造也会尝试拷贝
}int main() {std::cout << "--- Scenario 1: Return from function ---" << std::endl;BigData my_data = createBigData(); // 调用移动构造函数(如果优化不掉)std::cout << "\n--- Scenario 2: Explicit std::move ---" << std::endl;BigData data1(1000);BigData data2 = std::move(data1); // 使用std::move将左值强制转换为右值,// 从而调用移动构造函数。// data1此后不应再被使用!return 0;
}

代码关键点解释:
移动构造函数 BigData(BigData&& other):

参数类型是BigData&&,这是一个右值引用,它只能绑定到临时对象或即将被销毁的对象(“将亡值”)。

它的作用是“窃取”源对象other的资源。在这里,我们使用std::move(other.m_data)来移动其内部的vector。std::move的本质是一个static_cast,它将变量强制转换为右值,从而触发vector自身的移动构造函数。

noexcept关键字向标准库承诺这个操作不会抛出异常,这很重要,因为标准库容器(如std::vector)在重新分配内存时会优先使用noexcept的移动操作而不是拷贝操作来转移元素,性能更高。

移动赋值运算符 operator=(BigData&& other):

原理同移动构造函数,用于在赋值时移动资源。

std::move:

它本身不移动任何东西!它只是一个转换工具,告诉编译器:“我知道这个左值对象我不再需要了,请把它当作一个右值来处理”。

真正的移动操作是在类的移动构造函数或移动赋值运算符中完成的。

Rule of Five:

如果你定义了析构函数、拷贝构造函数、拷贝赋值运算符中的任何一个,那么你应该定义全部五个(加上移动构造函数和移动赋值运算符)来精确管理资源。在上面的例子中,我们显式定义了移动操作,并用= default保留了默认的拷贝操作。

现代编译器非常智能,通常会直接进行“返回值优化”,连移动构造都省略了。但在更复杂的返回路径中,移动语义是重要的保障。

实际应用:编译器的“神来之笔”与程序员的“安全网”

想象一下你要从A城市(函数内)运送一批贵重家具(大数据)到B城市(函数外)。

没有优化(C++03时代):你得先在A城找个仓库(函数栈帧)放家具,然后雇辆卡车,把家具一件件搬上卡车(拷贝),运到B城后,再一件件卸下来放到新家。费时费力!

有移动语义(C++11基础):你发现这些家具到了B城后,A城的仓库就要拆了。所以你很聪明,只把仓库的所有权转让给B城的人。你只需要把仓库地址告诉他(移动,即交换指针),他自己去取。省去了搬运的体力活!

返回值优化RVO(返回值优化)/NRVO(编译器优化):编译器这个“上帝”看到了你的整个计划。它直接说:“别在A城建仓库了,我直接在B城给你建好,你一开始就把家具放那里!”它完全消除了“移动”或“拷贝”这个动作本身。这是最极致的效率。

  1. 编译器的“神来之笔”:返回值优化
    返回值优化是编译器被标准允许的一种优化,它可以直接在函数外部(调用者的栈帧上)构造本应在函数内部返回的对象,从而完全避免任何拷贝或移动操作。

代码示例:

BigData createBigData() {BigData data(1000000); // 理论上,这里在函数内部构造`data`// ... 一些对data的操作return data;          // 理论上,这里需要将`data`返回出去
}int main() {BigData my_data = createBigData(); // 理论上,这里需要接收返回的`data`return 0;
}

未优化时的逻辑路径:

在createBigData函数内部调用构造函数BigData(1000000),创建data。

函数返回时,调用拷贝/移动构造函数,用data构造一个临时对象。

在main函数中,调用拷贝/移动构造函数,用临时对象构造my_data。

销毁临时对象。

函数结束,销毁data。

启用RVO/NRVO后的实际路径:
编译器会偷偷重写你的代码,变成类似这样:

void createBigData(BigData& hidden_obj) { // 编译器偷偷传进来一个引用hidden_obj.BigData(1000000); // 直接在目标位置构造!// ... 一些对hidden_obj的操作return; // 直接返回,没有任何拷贝!
}int main() {BigData my_data; // 只分配空间,未初始化createBigData(my_data); // 编译器偷偷把my_data的引用传进去构造return 0;
}

你看,my_data其实就是函数里的data,它们根本就是同一个对象! 这就是所谓的“连移动构造都省略了”,因为移动都不需要了。

“通常”这个词的含义:在现代编译器中,对于这种简单的返回局部对象的场景,RVO优化非常强大且几乎总是会发生(尤其是在Release模式下)。所以你可能在实际运行中看不到移动构造函数被调用。

  1. 程序员的“安全网”:移动语义的保障
    那么问题来了,既然编译器这么聪明,我们为什么还要费心写移动语义呢?

因为编译器不是万能的上帝,它只能在简单的、确定的代码路径中进行这种优化。一旦代码变得复杂,优化就可能失败。

BigData createBigData(bool flag) {BigData data1(1000000);BigData data2(1000000);if (flag) {return data1; // 可能返回这个分支} else {return data2; // 也可能返回那个分支}// 编译器懵了:我该在调用者那里预先构造data1还是data2?
}int main() {BigData my_data = createBigData(true); // RVO优化可能失败!return 0;
}
特性RVO(返回值优化)移动语义
实施者编译器程序员 (通过编写移动构造函数)
发生阶段编译时运行时
实现原理重新解释代码逻辑,直接在目标内存地址构造对象。资源所有权的转移(例如,交换指针)。
所需条件代码路径简单,符合标准要求。对象必须实现了移动构造函数/赋值运算符。
本质消除“拷贝/移动”这个操作本身将“昂贵的拷贝”操作替换为“廉价的移动”操作。
代码表现看不见任何调用(构造函数、移动构造都没有)。能看到移动构造函数被调用。

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

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

相关文章

中小企业SAP B1 HANA部署全解析:成本与云端优势

目录 云端部署成本构成与效益分析 软件许可费 硬件成本 服务费 培训费 技术优势 快速部署 弹性扩展 高可用性 云端部署适用场景 IT预算有限的中小企业 分布在不同地区的机构 需要快速上线的情况 本地部署适用场景 数据监管严格的行业 拥有完善IT基础设施企业 …

Django Channels实战:WebSocket实时通信开发

在当今Web应用开发中&#xff0c;实时通信功能已成为提升用户体验的关键要素。传统的HTTP请求-响应模式难以满足即时聊天、实时通知、协同编辑等场景的需求。本文将深入探讨如何利用Django Channels框架实现WebSocket通信&#xff0c;为你的Django项目添加实时交互能力。为什么…

大数据毕业设计选题推荐-基于大数据的懂车帝二手车数据分析系统-Spark-Hadoop-Bigdata

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

python 通过selenium调用chrome浏览器

更新selenium pip install -U selenium 下载浏览器和对应的驱动 Chrome for Testing availability 一般选稳定版本的&#xff0c;我是windows的就下win64的&#xff0c; 下载两个zip包后&#xff0c;把chromedriver.zip中的exe解压缩放到chrome_win64文件夹中 from selen…

Codeium:免费开源代码自动补全工具,高效管理代码片段告别开发卡壳

你有没有过这种尴尬时刻&#xff1f;写代码时突然想不起来常用的函数写法&#xff0c;比如 Python 的字典推导式&#xff0c;或者 MySQL 的联表查询语句&#xff0c;翻之前的项目文件翻半天&#xff0c;好不容易找到又得复制粘贴 —— 要是遇到换电脑&#xff0c;之前存的代码片…

嵌入式系统学习Day35(sqlite3数据库)

一.数据库 1、分类&#xff1a;大型中型小型 ORACLEMYSQL/MSSQL : SQLITE DBll powdb 关系型数据库 2、名词&#xff1a; DB数据库 select update database DBMS数据库管理系统 MIS管理信息系统 OA办公自动化 3、嵌入式数据库&#xff1a; sqlite3www.sqlite.org www.kernal.…

无人机自组网系统的抗干扰技术分析

由多个无人机和地面组成的MESH自组网系统是一种去中心化的无线通信网络 。系统由多个机载和地面通信终端构成&#xff0c;其核心特点是“无固定中心”&#xff0c;采用去中心化架构&#xff0c;所有节点地位平等 。在这种网状结构中&#xff0c;所有通信节点都能直接相互通信&a…

mac 安装 nginx

安装 nginx &#xff1a;brew install nginx检查 nginx 安装是否成功&#xff1a;nginx -vnginx version: nginx/1.29.1查看 nginx 启动状态&#xff1a;sudo brew services info nginx可以看到服务还未启动nginx (homebrew.mxcl.nginx)Running: ✘Loaded: ✘Schedulable: ✘ng…

JP4-7-MyLesson后台前端(四)

Java道经 - 项目 - MyLesson - 后台前端&#xff08;四&#xff09; 传送门&#xff1a;JP4-7-MyLesson后台前端&#xff08;一&#xff09; 传送门&#xff1a;JP4-7-MyLesson后台前端&#xff08;二&#xff09; 传送门&#xff1a;JP4-7-MyLesson后台前端&#xff08;三&am…

Linux control group笔记

Linux CGroup&#xff08;Control Groups&#xff09;是一个强大的内核功能&#xff0c;用于限制、记录和隔离进程组&#xff08;process groups&#xff09;使用的系统资源&#xff08;如 CPU、内存、磁盘 I/O、网络等&#xff09;。它通过将进程分组并对这些组进行资源分配和…

小迪Web自用笔记30

Node.js原生态的js运行在前端。Node.js&#xff1a;他与原生态JS最大的不同&#xff0c;就是前端只能看到输出的代码&#xff0c;而看不到jS文件req接收&#xff0c;res回显dirname获取绝对路径提交表单 &#xff1a;“Post路由” 到底是什么。这是一个非常核心的Web开发概念。…

并发编程的守护者:信号量与日志策略模式解析

一、信号量 关于信号量的介绍在深入Linux内核&#xff1a;IPC资源管理揭秘 这篇文章当中已经做了初步的介绍了&#xff0c;相信大家对于信号量已经有了初步的认知了。 今天&#xff0c;我们就来探讨如何实现信号量。 1. 信号量的接口 //初始化信号量 //成功了&#xff0c;返…

conda 创建环境嵌套报错

使用conda create --prefix /path可以成功创建&#xff0c;有可能时默认路径冲突导致的 conda config --show 发现&#xff1a; envs_dirs: /root/autodl-tmp/miniconda3/envs/envs_test/path/root/autodl-tmp/miniconda3/envs/root/.conda/envs 未显式指定环境路径&#xf…

低代码核心原理总结

Web 低代码平台核心原理深度解析 1. 架构总览 Web低代码平台的核心架构包含四个关键层次&#xff1a; class LowCodePlatform {constructor() {this.visualEditor new VisualEditor(); // 可视化编辑器this.metaDataEngine new MetaDataEngine(); // 元数据引擎this.code…

操作系统研发工作心得体会 - 于复杂性中构建秩序

在操作系统&#xff08;OS&#xff09;研发这片要求极致严谨与创新的工程深海中航行数载&#xff0c;我的角色从一个纯粹的技术专家&#xff0c;逐渐演变为一个需要兼顾技术深度、系统广度与团队效能的复合型角色。这段旅程&#xff0c;让我深刻体会到&#xff0c;构建一个成功…

Excel 表格 - Excel 减少干扰、专注于内容的查看方式

Excel 减少干扰、专注于内容的查看方式 1、隐藏元素 点击 【视图】 -> 取消勾选 【网格线】 -> 取消勾选 【编辑栏】 -> 取消勾选 【标题】2、全屏显示 点击 【功能区显示选项】&#xff08;工具栏右下角小箭头&#xff09; -> 点击 【全屏模式】

C# Web API 前端传入参数时间为Utc

Web API 前端传入参数时间为Utc&#xff08;时间相差8个小时&#xff09;1.在Program.csbuilder.Services.AddControllers().AddJsonOptions(options > {// 序列化时将时间转换为本地时间&#xff08;北京时间&#xff09;options.JsonSerializerOptions.Converters.Add(new…

AI Agent开发入门:Semantic Kernel构建智能邮件助手

点击 “AladdinEdu&#xff0c;同学们用得起的【H卡】算力平台”&#xff0c;H卡级别算力&#xff0c;80G大显存&#xff0c;按量计费&#xff0c;灵活弹性&#xff0c;顶级配置&#xff0c;学生更享专属优惠。 引言&#xff1a;AI Agent——下一代人机交互范式 在人工智能技术…

WebAssembly:开启高性能 Web 应用的新篇章

在互联网技术飞速发展的浪潮中&#xff0c;Web应用的性能一直是一个重要的优化目标。传统的JavaScript虽然灵活便捷&#xff0c;但在处理CPU密集型任务时&#xff0c;其性能瓶颈日益凸显&#xff0c;限制了Web应用在游戏、音视频编辑、科学计算、图像处理等高性能领域的深入发展…

001-003 产品经理-ML应用构建-ML应用范围

001-003 产品经理-ML应用构建-ML应用范围 时间&#xff1a;2025年09月08日14:48:01 备注&#xff1a;笔记回顾和复习&#xff0c;仅用于分享而非商用&#xff0c;引用内容若侵权请联系并删除。 文章目录001-003 产品经理-ML应用构建-ML应用范围导引 学习法则1 内容索引 产品经…