当前位置: 首页 > news >正文

【C++11】右值引用、移动构造、移动赋值、完美转发 的原理介绍

文章目录

  • 一、概念
    • 1.1 左值
    • 1.2 左值引用
    • 1.3 什么是右值?
    • 1.4 什么是右值引用?
      • 对于参数左值还是右值的不同,是被重载支持的
      • 左值引用的使用场景 和 缺陷
  • 二、移动语义
    • 2.1 移动拷贝构造
    • 2.2 移动赋值
  • 三、右值引用 与 STL
    • 3.1 移动拷贝构造 和 赋值重载
    • 3.2 插入接口
    • 3.3 完美转发、万能引用
      • 完美转发
      • 万能引用


传统的 C++ 语法中就有引用的语法,而 C++11 中新增了的右值引用语法特性,所以我们管之前的引用叫做左值引用。无论左值引用还是右值引用,都是给对象取别名


一、概念

1.1 左值

  • 左值是一个表示数据的表达式(如变量名或解引用的指针),通俗的理解就是,能取到地址的就是左值

  • 正常情况下我们可以对左值赋值,定义 const 修饰后的左值,不能给它赋值,但是可以取出它的地址;

  • 左值可以出现在赋值符号 " = " 的左边,也可以出现在赋值符号的右边;

  • 左值具有持久的状态。

// 常见的左值
int a = 0;
int b = 1;
int* p = &a;
const int c = 3;

1.2 左值引用

  • 左值引用:就是对左值的引用,相当于给左值取别名。
  • 左值引用只能引用左值,不能直接引用右值;但是 const 左值引用可以引用右值。
  • 左值引用符号 &
// 左值引用给左值取别名
int& ref1 = a;// 左值引用给右值取别名
// int& ref2 = (a + b);			// err...(a+b)返回的是临时对象,临时对象具有常性,出现权限放大问题
const int& ref2 = (a + b);		// 加 const 就行了

1.3 什么是右值?

  • 右值也是一个数据表达式,右值是 字面常量 或者是 求值过程种创建的临时对象

  • 对于右值,不能取出地址,不能对它赋值

  • 右值的生命周期是短暂的,如:字面常量,表达式返回值,函数返回值(不是左值引用的返回值),临时变量,匿名对象…;

// 右值
a + b;
func(x,y);
10;
"abcd";

1.4 什么是右值引用?

  • 右值引用相当于给右值起别名,右值引用只能右值,除非左值 move 后,可以对其进行右值引用
  • 右值是没有地址的,但是右值引用后,这个右值引用会被存到特定的位置,且可以取到该值的地址,也就是说 右值引用值是一个左值
  • 右值引用会开辟一块空间去存右值,普通的右值引用可以修改这块空间,const 的右值引用则不可以被修改。
  • 右值引用符号 &&

move():标准库中的函数,可以将左值强制转换为右值

// 右值引用给右值取别名:
int&& ref3 = (a + b);			// 右值引用不能给左值起别名:
//int&& ref4 = a;		// err// 右值引用可以 给 move 后的左值取别名:
int&& ref4 = move(a);

对于参数左值还是右值的不同,是被重载支持的

//void func(const int& a)	// 这样虽说都可以使用,但是区分不了左值和右值
void func(int& a)
{cout << "void func(int& a)" << endl;
}void func(int&& a)
{cout << "void func(int&& a)" << endl;
}int main()
{int a = 0;int b = 1;func(a);func(a + b);return 0;
}
----------------------------------
输出结果:
void func(int& a)
void func(int&& a)

左值引用的使用场景 和 缺陷

左值引用可以直接减少拷贝。应用如下:

  1. 左值引用传参;
  2. 传引用返回。

左值引用解决了大多数场景的问题,但也存在一些解决不了的问题:

  1. 局部对象返回问题;
  2. 对象深拷贝问题。

C++11 前,对于带指针的容器(比如 string)会进行深拷贝,深拷贝的目的是让赋值和被赋值的对象,都正常保存并使用数据互不影响,代价比较高。而如果被拷贝对象是右值,右值本不需要留存浅拷贝就可以,但还是进行了深拷贝,是很不划算的。

ttang::string s1("hello world");ttang::string ret1 = s1;		// 左值拷贝
ttang::string ret2 = (s1+'!');	// 右值拷贝,如果也是深拷贝,很不划算
// 实际上右值拷贝,C++11使用的是移动拷贝构造,是浅拷贝
// 可以很大程度的优化右值的拷贝效率

二、移动语义

2.1 移动拷贝构造

移动拷贝构造函数跟构造函数一样,参数需要是一个本身类型的对象,但 移动拷贝构造函数的参数是一个该类型的右值引用

所谓移动,是数据交换。移动拷贝函数创建出一个新对象,将新对象中的值都设置为 0,接下来与传进来的右值对象进行资源交换

// 移动拷贝构造
string(string&& s):_str(nullptr),_size(0),_capacity(0)
{swap(s);
}
  • 根据函数匹配规则,如果调用拷贝构造对象的时候传的是左值,编译器会自动匹配到拷贝构造函数,如果传的是右值,那么就会匹配到移动拷贝函数。

  • 使用移动拷贝构造函数后,源对象指向资源就被交换出去,这些资源的所有权都归属到了新对象。
    因此,如果源对象是一个长期存在的对象的时候,需要谨慎使用移动拷贝构造函数。调用移动拷贝构造函数创建出 s3,s1 的资源被转移到了 s3,s1 中没有指向任何资源,所以就不能通过 s1 去寻找之前的资源。

  • 如果只是调用 move() 函数,而不对其返回值进行接收,是不会改变传入值的内容的。

std::string s1("hello");// 拷贝
std::string s2 = s1;
// 移动(s3 里面存了“hello”,而 s1 空了)
std::string s3 = move(s1);// 只是这样不接受其返回值,是不会改变s2的
move(s2);
std::string s4 = s2;

一些使用举例:

list<ttang::string> lt;ttang::string s1("hello world");		
lt.push_back(s1);							// 深拷贝
lt.push_back(move(s1));						// 移动lt.push_back(ttang::string("hello world"));	// 移动,匿名对象也是右值
lt.push_back("hello world");				// 移动

总的看来:

  • 左值引用减少拷贝,右值引用也是减少拷贝,提高效率;
  • 但是他们的角度不同,左值引用是直接减少拷贝;右值引用是间接减少拷贝,通过函数重载,识别出是左值还是右值,如果是右值,则不再深拷贝,直接移动拷贝,提高效率。

2.2 移动赋值

对于初始化:

  • 如果 string 类中只有一个移动拷贝构造函数,那么函数返回值构造临新对象的时候,那么只需要调用一次 移动拷贝构造 函数将资源转移给新对象。

对于已经初始化过的对象进行赋值:

  • 那么就会调用一次 移动拷贝构造 函数和一次 赋值重载 函数,赋值重载函数也是进行深拷贝的。

因此,为了解决赋值重载的深拷贝的问题,我们还需要再实现一个移动赋值重载函数,移动赋值重载 函数跟拷贝构造函数一样,都是 解决深拷贝的问题,都是进行转移资源

移动赋值重载函数跟赋值重载函数的定义是类似的,只是移动赋值重载函数的参数是右值引用,是为了让右值能够调用该函数,如下:

// 移动赋值
string& operator=(string&& s)
{swap(s);return *this;
}

三、右值引用 与 STL

综上推演,C++11 为很多容器都增加了 移动拷贝构造函数赋值重载函数的右值引用版本,包括 push_back 或者 insert 接口也增加了右值引用 的重载

3.1 移动拷贝构造 和 赋值重载

在这里插入图片描述
在这里插入图片描述

3.2 插入接口

在这里插入图片描述
在这里插入图片描述

上面的文档中,insert 和 push_back 可以接收 const 左值引用,也就是说这个函数既可以接收左值,也可以接受右值,那么为什么还需要多定义一个参数是右值引用的函数重载呢?

  • 因为,我们说了左值引用可以接收右值但是相应的拷贝任然是深拷贝,重载右值引用版本正是为了优化这一点。
  • 再梳理一下传入右值的流程:如果调用 vector 中 insert 时,传的参数是右值,那么就会编译器就会匹配到右值引用参数的 insert 的重载函数,因为 insert 函数内部会对该参数值进行赋值重载到 vector 内,如果是右值,那么就会调用移动拷贝构造函数,所以可以避免深拷贝的出现。
vector<string> v;
string str("hello");
v.insert(v.end(), str);	// str 是左值,深拷贝
v.insert(v.end(), string("hello~"))	// string("xx") 是匿名对象,调用右值版本
  • 因此,我们在赋值、构造、以及调用 insert、push_back、时,如果涉及深拷贝的问题,尽量传右值(匿名对象),这样可以减少深拷贝的问题。

3.3 完美转发、万能引用

完美转发

关于右值引用本身的属性,举例:

int&& a = 10;
cout << &a << endl;	// a 可取地址
a++;				// a 可修改右值引用的 a,虽然是通过右值得到的,但 a 本身是左值!!!

进一步举例:

void insert(iterator pos, const T&& val)
{//...*pos = val;	// val 是左值,调用的是赋值重载++_end;
}

很明显的,在上面调用参数是右值引用的 insert 接口中存在一个问题,如果右值引用 val 去接受一个右值,那么这个 val 就会退化成一个左值。所以 *pos = val; 这一步调用的还是重载函数,不是移动拷贝构造。因此,为了保持 val 是一个右值,有一个专门的写法:

std::forward<T>(val):它可以在传参的时候保持 val 原生属性,也就是可以保持其右值属性,因此,这样可以保证 *pos = val; 调用的会是移动赋值重载函数。

这种调用 std::forward(val),使得 val 保持原生属性的过程就是 完美转发,写成如下:

void insert(iterator pos, const T&& val)
{//...*pos = std::forward<T>(val);	// 保持了 val 的右值属性!调用的就是其移动赋值函数++_end;
}

万能引用

万能引用 又叫 引用折叠,使用 && 进行定义或申明。万能引用定义的参数,即可以对左值引用,也可以对右值引用。

使用场景:函数模板的形参auto 声明

template<typename T> 
void f(T&& param);   //param是个万能引用auto && var2 = var1;

以上两种场景的共同之处,在于它们都涉及类型推导。

下面这两种只是普通的右值引用:

template<typename T> 
void f(std::vector<T>&& param); // param 是右值引用template<typename T> 
void f(const T&& param);   		// 加 const 则是右值引用

对于万能引用和右值引用的区分,不必过多纠结~ 能用就行。

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }template<typename T>
void PerfectForward(T&& t)	// 这里面用这个右值转化的左值 t 可以,但是再转一层就会出问题
{Fun(forward<T>(t));		// 不加完美转发的话,会全是左值引用!!!
}int main()
{int a;PerfectForward(a);            // 左值PerfectForward(std::move(a)); // 右值PerfectForward(10);           // 右值const int b = 8;PerfectForward(b);		      // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}

🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~


相关文章:

【C++11】右值引用、移动构造、移动赋值、完美转发 的原理介绍

文章目录 一、概念1.1 左值1.2 左值引用1.3 什么是右值&#xff1f;1.4 什么是右值引用&#xff1f;对于参数左值还是右值的不同&#xff0c;是被重载支持的左值引用的使用场景 和 缺陷 二、移动语义2.1 移动拷贝构造2.2 移动赋值 三、右值引用 与 STL3.1 移动拷贝构造 和 赋值…...

Python【理解标识符的定义】

标识符是用来表示变量、函数、类、模块等命名实体的名称。它是由字母&#xff08;大小写均可&#xff09;、数字和下划线组成的字符串&#xff0c;遵循一定的命名规则和约定。以下是标识符的定义&#xff1a; 标识符必须以字母&#xff08;大小写均可&#xff09;或下划线(_)开…...

AR智能眼镜主板设计方案_AR眼镜PCB板设计

AR智能眼镜是一种采用先进技术的创新产品&#xff0c;具备强大的功能和性能。它采用了MTK8788八核 12nm低功耗硬件平台&#xff0c;搭载IMG GE830063OMhz或以上的GPU&#xff0c;并运行Android 11.0或以上的操作系统。该眼镜支持光波导1080P显示和LVDS接口自由曲面显示&#xf…...

【SA8295P 源码分析 (三)】79 - AIS Camera Event 事件处理函数 AisEngine::EventHandler() 源码分析

【SA8295P 源码分析】79 - AIS Camera Event 事件处理函数 AisEngine::EventHandler 源码分析 一、AIS Camera Event 事件处理函数 AisEngine::EventHandler()二、AisEngine::ProcessEvent() 函数负责处理哪些 Event 事件?三、pEngine->m_eventHandlerSignal 事件什么情况下…...

Web安全测试详解

前言 随着互联网时代的蓬勃发展&#xff0c;基于Web环境下的应用系统、应用软件也得到了越来越广泛的使用。 目前&#xff0c;很多企业的业务发展都依赖于互联网&#xff0c;比如&#xff0c;网上银行、网络购物、网络游戏等。但&#xff0c;由于很多恶意攻击者想通过截获他人…...

react配置 axios

配置步骤&#xff08;基本配置&#xff09;&#xff1a; 1.安装 axios cnpm install axios --save2.src/utils 新建一个 request.js文件(没有utils就新建一个目录然后再建一个request.js) 3.request代码如下&#xff1a; 这个是最简单的配置了&#xff0c;你可以根据自己的需…...

【图解RabbitMQ-5】RabbitMQ Web管控台图文介绍

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;CSDN实力新星&#xff0c;后端开发两年经验&#xff0c;曾担任甲方技术代表&#xff0c;业余独自创办智源恩创网络科技工作室。会点点Java相关技术栈、帆软报表、低代码平台快速开…...

GoogleNet论文精读

论文名&#xff1a;Going depper with convolutions论文下载地址&#xff1a;https://github.com/jixiuy/paper引言第一段&#xff1a;背景成绩1*1的卷积在channel上升维和降维&#xff0c;channel融合&#xff0c;计算方法上等价于FNNGAP&#xff08;全局平均池化&#xff09;…...

智能指针shared_ptr简介及小例子

shared_ptr是一种智能指针&#xff0c;用于处理动态分配的对象。它提供了一种引用计数的机制&#xff0c;当没有任何其他shared_ptr指向一个对象时&#xff0c;该对象将被自动删除。 shared_ptr的作用类似于常规指针&#xff0c;但有一些额外的功能。它能够记录有多少个shared…...

机器人精确移动包

move_near 之前有写过, 将ROS官方的move_basic包改写成了python形式, 同时将它写成了一个完整的action接口 最近测试时发现了问题, odom的数据波动可能会导致机器人陷入正反馈从而一直移动 具体表现为: 机器人移动精度设置为0.005 [m] 机器人在移动到接近0.005的位置, odom…...

强化学习环境报错解决

问题&#xff1a;nameerror: name ‘glpushmatrix‘ is not defined 解决&#xff1a;更换pyglet包的版本。pyglet2.0a4会报这个错误&#xff0c;把版本换成pyglet1.5.27即可。 问题&#xff1a;安装了gym和ale-py但是还是找不到模型&#xff0c;报错ModuleNotFoundError: No…...

Ganache本地测试网如何在远程环境中进行访问和操作

文章目录 前言1. 安装Ganache2. 安装cpolar3. 创建公网地址4. 公网访问连接5. 固定公网地址 前言 Ganache 是DApp的测试网络&#xff0c;提供图形化界面&#xff0c;log日志等&#xff1b;智能合约部署时需要连接测试网络。 Ganache 是一个运行在本地测试的网络,通过结合cpol…...

Kotlin中的函数分类(顶层、成员、局部、递归等)

在 Kotlin 中&#xff0c;函数可以按照不同的方式进行分类。在本篇博客中&#xff0c;我们将介绍以下几种常见的函数分类&#xff0c;并提供示例代码进行演示。 顶层函数&#xff1a; 顶层函数是指定义在文件中的函数&#xff0c;不依赖于任何类或对象。它们可以在文件的任何…...

字符串排序程序

字符串排序程序&#xff0c;对一个字符串中的数值进行从小到大的排序 例如排序前给定的字符串为" 20 78 9 -7 88 36 29" 排序后&#xff1a; -7 9 20 29 36 78 88 要求使用包装类对数值类型的字符串转换成整型进行排序。 public class StringSort {public static vo…...

功率放大器在材料测试中的应用有哪些

功率放大器在材料测试中有广泛的应用&#xff0c;尤其在材料的物理、电子和热学性质等方面的研究中起到了重要的作用。下面Aigtek安泰将详细介绍功率放大器在材料测试中的一些主要应用。 电学特性测试&#xff1a;功率放大器用于材料的电学特性测试&#xff0c;如电导率、介电常…...

汽车屏类产品(一):流媒体后视镜Camera Monitoring System (CMS)

前言: CMS,有叫电子侧视镜,虚拟倒车镜,电子倒车镜, 电子取代镜等,ISO 国际标准组织称其为摄像头监控系统。电子后视镜由“摄像头+屏幕”组成,汽车外后视镜经历了光学镜面从平面镜到曲面镜的迭代进步,CMS也实现从商用车到乘用车的过渡。显示模式为外部摄像头采集图像,…...

三元组(C++ 实现矩阵快速转置)

三元组稀疏矩阵是一种高效存储稀疏矩阵的方法。它通过记录矩阵中非零元素的行、列和值来表示一个稀疏矩阵。我们在三元组里存储的是每个元素的行、列以及值。 题目&#xff1a; 任意输入一个稀疏矩阵M&#xff0c;用三元组顺序表压缩存储该稀疏矩阵M&#xff0c;然后求其转置矩…...

Apriori(关联规则挖掘算法)

关联规则分析 事务库 上表所示的购物篮数据即是一个事务库&#xff0c;该事务库记录的是用户行为的数据。 事务 上表事务库中的每一条记录被称为一笔事务。在购物篮事务中&#xff0c;每一次购物行为即为一笔事务&#xff0c;例如第一行数据“用户1购买商品A,B,C”即为一条事…...

new Object()到底占用几个字节

Java内存模型 对象内存中可以分为三块区域&#xff1a;对象头(Header)&#xff0c;实例数据(Instance Data)和对齐填充(Padding)&#xff0c;以64位操作系统为例(未开启指针压缩的情况)Java对象布局 如下图所示&#xff1a; 其中对象头中的Mark Word中的详细信息在文章synchr…...

瞬态抑制二极管TVS的工作原理?|深圳比创达电子EMC(上)

TVS二极管具有响应速度快、漏电流小、钳位电压稳以及无寿命衰减的特性&#xff0c;从小到信号线静电防护&#xff0c;大到电力系统抗雷击浪涌&#xff0c;TVS都发挥着至关重要的作用。本章对瞬态抑制二极管TVS工作机理展开分析&#xff0c;供产品选型参考。接下来就跟着深圳比创…...

Jimeng AI Studio实现Web爬虫:数据采集自动化方案

Jimeng AI Studio实现Web爬虫&#xff1a;数据采集自动化方案 1. 项目背景与需求 电商公司每天需要从多个网站采集商品信息&#xff0c;传统的手工复制粘贴方式效率低下&#xff0c;而且容易出错。技术团队需要处理上百个商品页面的数据&#xff0c;包括价格、库存、描述和用…...

十字军之王II双字节字符显示解决方案:从乱码到完美支持的技术实现

十字军之王II双字节字符显示解决方案&#xff1a;从乱码到完美支持的技术实现 【免费下载链接】CK2dll Crusader Kings II double byte patch /production : 3.3.4 /dev : 3.3.4 项目地址: https://gitcode.com/gh_mirrors/ck/CK2dll 当《十字军之王II》玩家第一次在游戏…...

OpenClaw代码助手:Qwen3-14b_int4_awq实现的自动补全与错误检查

OpenClaw代码助手&#xff1a;Qwen3-14b_int4_awq实现的自动补全与错误检查 1. 为什么需要本地化代码助手&#xff1f; 作为一名长期与代码打交道的开发者&#xff0c;我一直在寻找能够提升编程效率的工具。传统的IDE插件虽然能提供基础补全&#xff0c;但存在几个痛点&#…...

李慕婉-仙逆-造相Z-Turbo模型安装包制作教程

李慕婉-仙逆-造相Z-Turbo模型安装包制作教程 1. 开篇&#xff1a;为什么需要制作安装包 如果你已经体验过李慕婉-仙逆-造相Z-Turbo模型的文生图能力&#xff0c;可能会发现每次部署都需要重复安装依赖、配置环境。制作安装包就是为了解决这个问题&#xff0c;让模型可以一键安…...

从下载到调用:AutoGLM-Phone-9B完整部署与OpenAI接口对接实战

从下载到调用&#xff1a;AutoGLM-Phone-9B完整部署与OpenAI接口对接实战 1. 为什么你需要关注AutoGLM-Phone-9B 想象一下&#xff0c;你正在开发一款手机应用&#xff0c;需要让用户上传一张照片&#xff0c;然后AI不仅能看懂照片内容&#xff0c;还能用语音回答用户的问题&…...

实测科哥版HeyGem稳定性:文件校验、中断恢复、显存保护全解析

实测科哥版HeyGem稳定性&#xff1a;文件校验、中断恢复、显存保护全解析 1. 引言&#xff1a;为什么稳定性对数字人视频生成如此重要 在数字人视频生成领域&#xff0c;我们常常关注生成效果和速度&#xff0c;却容易忽视一个更基础的问题——稳定性。想象一下&#xff0c;当…...

霜儿-汉服-造相Z-Turbo科研辅助:使用LaTeX撰写包含AI生成图像的学术论文

霜儿-汉服-造相Z-Turbo科研辅助&#xff1a;使用LaTeX撰写包含AI生成图像的学术论文 最近在帮一位研究传统服饰的朋友整理论文&#xff0c;遇到了一个挺有意思的问题。他们需要大量汉服的结构示意图和纹样分析图&#xff0c;但手绘耗时&#xff0c;找现成资料又很难完全匹配研…...

DeepSeek总结的DuckLake 中的数据内联:为数据湖解锁流式处理

原文地址&#xff1a;https://ducklake.select/2026/04/02/data-inlining-in-ducklake/ DuckLake 中的数据内联&#xff1a;为数据湖解锁流式处理 Pedro Holanda 2026-04-02 TL;DR&#xff1a; DuckLake 的数据内联功能将小批量更新直接存储在目录中&#xff0c;从而消除了“小…...

OpenClaw硬件适配指南:gemma-3-12b-it在不同显卡上的性能对比

OpenClaw硬件适配指南&#xff1a;gemma-3-12b-it在不同显卡上的性能对比 1. 测试背景与动机 上周在本地部署OpenClaw对接gemma-3-12b-it模型时&#xff0c;发现同样的自动化任务在不同设备上表现差异巨大。我的旧笔记本&#xff08;RTX 3060&#xff09;处理简单文件整理都会…...

程序员因简单自动化放弃Python转C,底层逻辑令人震撼

一、一个“简单自动化”&#xff0c;逼得程序员放弃Python转C 拥有一个共识的程序员是很多的&#xff0c;那就是Python、JavaScript上手速度快&#xff0c;还省力&#xff0c;进行写自动化工具完全就是“降维打击”&#xff0c;又有谁会花费力气去写晦涩到难以理解的C语言呢&am…...