C++11—右值引用

目录

简介 

左值和右值

左值

右值

右值引用

生命周期

引用折叠

实际应用

移动语义

移动构造函数

移动赋值运算符

完美转发


简介 

     之前我们曾学习过引用叫左值引用,但那是C++98的,在C++11中新增了一种引用叫右值引用。右值引用主要用于支持移动语义和完美转发。从而可以更高效地管理资源,减少不必要的复制操作。

左值和右值

左值

     左值是指可以取地址的表达式。它通常出现在赋值操作符左侧。

下面是它的几个特性:

  1. 可寻址性:左值在内存中有明确的地址,即可以取地址。
  2. 持久性:左值通常指向内存中的持久对象,这些对象在表达式结束后仍然存在。
  3. 可变性:左值可以被重新赋值,即可以改变其存储的值。

     例:

int a = 0;
int* p = new int[10];

     上面的a就是左值 ,因为它有持久的内存地址,并且可以被后续的代码访问和修改。*p同理。

右值

     右值是指不可以取地址的临时对象,它通常出现在表达式的结果。

下面是它的几个特性:

  1. 不可寻址性:右值通常没有持久的内存地址,即不可以取地址。
  2. 短暂性:右值通常表示的是一些临时的值,这些值在表达式结束后就会被销毁。

      例:

int a = 0;
int b = 1; 
int c = a + b;

     上面的0,1就是右值 ,因为它是一个字面常量,没有持久的内存地址。a + b也是右值,因为它是表达式的结果,没有持久的内存地址。

常见的右值有:

  1. 字面常量,如整数常量(如42)、字符常量('a')等
  2. 无引用的函数返回值。
  3. 表达式的结果,如算术表达式(如1+1)、逻辑表达式(如a||b)、关系表达式(如a>b)。

     想区分它们也很简单只要能取地址就是左值,不能取地址就是右值。 

右值引用

     右值引用的表示如下:

int&& a = 1;

     右值引用和左值引用没有什么区别都是起别名,从汇编层来看它们都是用指针来实现的。只不过一个是给右值起别名一个给左值。

     虽然说右值引用不能引用左值,但是可以通过move函数来实现。该函数可以让左值变右值,本质上是通过强制类型转换(使用该函数要小心原对象会变成不确定状态,原因一会儿在解答)。

int a = 1;
int&& ra = move(a);

同样的左值引用也可以引用右值在前面加const,即const左值引用。当const左值引用绑定右值时,编译器通常会创建一个临时对象,并将右值的内容复制到该临时对象中。又因为临时对象有只读性,被const修饰后的对象也只具有只读性。这是权限的平移,所以可以这样是OK的。

const int& a = 1;

     注意:一个右值被右值引用绑定后,右值引用变量表达式的属性是左值!就像上面的ra虽然它是一个右值引用,但它自身作为一个变量是左值。

生命周期

     大家可能发现了当一个临时的或匿名的对象被右值引用后,它好像不仅仅可以出现在该行,其它行好像也可以出现。事实上当一个对象被右值引用后它的生命周期就被延长了。不仅仅是右值引用,const左值引用的对象也是可以实现的只不过内容无法修改。例如:

int main()
{
	string&& str1 = string("ha");
	str1 += "ha";
	cout << "str1: " << str1 << endl;

	const string& str2 = string("xi");
	cout << "str2: " << str2 << endl;

	return 0;
}

引用折叠

     它的规则是:右值引用的右值引用折叠成右值引用,其他组合均折叠成左值引用。简单来说如果是两个都是右值引用那就折叠成右值引用,其他就都是左值引用。这主要是为了实现万能引用。示例:

template<class T>
void Test(T&& x)
{
	//...
}

     注意上面的参数虽然是通过右值引用传递的,因为它是一个模板所以这是一个万能引用。不太懂的可以先往下看。当用下面代码调用时:

Test(10);

      由于Test函数模板的参数是T&&,这里发生了模板参数推导即转发引用。简单来说当传递一个左值给接受T&&参数的函数模板时,编译器会推导T为左值引用类型;右值即非引用类型。

     在这种情况下,编译器会T推导为int(因为10是int类型),并且由于10是右值,T&&在这里实际上变成了int&&,即一个对int类型的右值引用。这其实并没有用引用折叠。

     用下面的代码调用时:

int a = 10;
Test(a);

    调用 Test 函数并将a作为参数传递进去。由于传递的是左值, T会被推导为int&,这就变为了int& && 然后根据引用折叠规则最后就变为了int&,即一个对int类型的左值引用。

     这样一份代码就可以实现左值引用和右值引用,是不是方便了不少。

实际应用

移动语义

     移动语义指的是通过移动构造函数和移动赋值运算符,将资源的所有权从一个对象转移到另一个对象,即新对象直接管理原对象的内存资源,原对象仍然处于有效状态,但不再持有任何资源。它们的主要目的是优化资源管理和提升程序性能。

移动构造函数

     可以简单认为移动构造函数的实现是通过下面的方式:

void swap(string& s)
{
	std::swap(_str, s._str);//首元素地址
	std::swap(_size, s._size);//字符串长度
	std::swap(_capacity, s._capacity);//容量
}
// 移动构造 
string(string&& rs)
{
	swap(rs);
}

     rs是右值引用但它的属性是左值,所以在swap()里用的是左值引用。

     示例: 

string Test()
{
	string str("Happy New Year");
	//...
	return str;
}

int main()
{
	string ret = Test();
	return 0;
}

     不需要关心上面的函数是用来做什么的,这里我就只拿关键的部分。之前要想把str的值传给ret就要先创建一个临时对象,然后把str里的值拷贝构造给临时对象,然后临时对象在拷贝构造给ret。

     使用两次拷贝构造这代价可不小,要是使用移动语义的话可就不一样了。移动语义相当于“掠夺”并不进行拷贝,这代价就很小了。

移动赋值运算符

     它的简单实现和移动构造函数可以说是一样的。

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

     示例:

string Test()
{
    string str("Happy New Year");
    //...
    return str;
}

int main()
{
    string ret;
    ret = Test();
    return 0;
}

     之前要想把str的值传给ret就要先创建一个临时对象,然后把str里的值拷贝构造给临时对象,然后临时对象在拷贝赋值给ret。 

     当有了移动语义后就只是进行简单交换。

     上面我所介绍的str值传给ret的过程,是编译器没有进行任何优化。我在这里就不介绍编译器具体怎么优化它们了,大家感兴趣可以自己上网查查。 

     现在我解答一下为什么使用move()后,原对象通常处于不确定状态。这是因为当move()把对象转换为右值后,会自动调用移动语义而造成的。例如:

int main()
{
	string s("haha");
	cout << "s:" << s << endl;

	string rs = move(s);
	cout << "s:" << s << endl;
	
	return 0;
}

完美转发

     完美转发在C++编程中具有重要意义,它提高了代码的复用性、性能和可维护性。先看如下代码:

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

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

template<class T>
void Test(T&& x)
{
	Fun(x);
}

int main()
{
	int a = 1;
	Test(a);
	Test(1);
	return 0;
}

     这是运行结果: 

     为什么第二个也是左值引用?我在上面提到过右值引用它自身作为一个变量是左值,所以这里就是左值引用。但是这样会降低运行效率,其解决方案就是用完美转发forward函数,它能够保持参数的左、右值属性不变,并将其转发给目标函数。具体使用方法如下:

template<class T>
void Test(T&& x)
{
	Fun(forward<T>(x));
}

     修改后的结果: 

     完美转发forward本质是⼀个函数模板,他主要还是通过引用折叠的方式实现,上面示例中传递给Func的实参是右值,T被推导为int,没有折叠,forward内部 x 被强转为右值引用返回。当传递给Func的实参是左值,T被推导为int&,引用折叠为左值引用,forward内部 x 被强转为左值引用返回。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu下的Doxygen+VScode实现C/C++接口文档自动生成

Ubuntu下的DoxygenVScode实现C/C接口文档自动生成 Chapter1 Ubuntu下的DoxygenVScode实现C/C接口文档自动生成1、 Doxygen简介1. 安装Doxygen1&#xff09;方法一&#xff1a;2&#xff09;方法二&#xff1a;2. doxygen注释自动生成插件3. doxygen注释基本语法4. doxygen的生成…

函数与递归

函数与递归 声明或者定义应该在使用之前&#xff08;不单单针对于函数&#xff09; 函数对全局变量做出的改变还是不会随着函数结束而消失的 函数声明在main函数里面也是可以的 引用变量和引用实体的变化是一样的 传址调用比传值调用效率高 重载函数->编译器会根据传递…

网络编程套接字(中)

文章目录 &#x1f34f;简单的TCP网络程序服务端创建套接字服务端绑定服务端监听服务端获取连接服务端处理请求客户端创建套接字客户端连接服务器客户端发起请求服务器测试单执行流服务器的弊端 &#x1f350;多进程版的TCP网络程序捕捉SIGCHLD信号让孙子进程提供服务 &#x1…

96,【4】 buuctf web [BJDCTF2020]EzPHP

进入靶场 查看源代码 GFXEIM3YFZYGQ4A 一看就是编码后的 1nD3x.php 访问 得到源代码 <?php // 高亮显示当前 PHP 文件的源代码&#xff0c;用于调试或展示代码结构 highlight_file(__FILE__); // 关闭所有 PHP 错误报告&#xff0c;防止错误信息泄露可能的安全漏洞 erro…

C++模板编程——可变参函数模板之折叠表达式

目录 1. 什么是折叠表达式 2. 一元左折 3. 一元右折 4. 二元左折 5. 二元右折 6. 后记 上一节主要讲解了可变参函数模板和参数包展开&#xff0c;这一节主要讲一下折叠表达式。 1. 什么是折叠表达式 折叠表达式是C17中引入的概念&#xff0c;引入折叠表达式的目的是为了…

如何用微信小程序写春联

​ 生活没有模板,只需心灯一盏。 如果笑能让你释然,那就开怀一笑;如果哭能让你减压,那就让泪水流下来。如果沉默是金,那就不用解释;如果放下能更好地前行,就别再扛着。 一、引入 Vant UI 1、通过 npm 安装 npm i @vant/weapp -S --production​​ 2、修改 app.json …

openRv1126 AI算法部署实战之——TensorFlow TFLite Pytorch ONNX等模型转换实战

Conda简介 查看当前系统的环境列表 conda env list base为基础环境 py3.6-rknn-1.7.3为模型转换环境&#xff0c;rknn-toolkit版本V1.7.3&#xff0c;python版本3.6 py3.6-tensorflow-2.5.0为tensorflow模型训练环境&#xff0c;tensorflow版本2.5.0&#xff0c;python版本…

电介质超表面中指定涡旋的非线性生成

涡旋光束在众多领域具有重要应用&#xff0c;但传统光学器件产生涡旋光束的方式限制了其在集成系统中的应用。超表面的出现为涡旋光束的产生带来了新的可能性&#xff0c;尤其是在非线性领域&#xff0c;尽管近些年来已经有一些研究&#xff0c;但仍存在诸多问题&#xff0c;如…

Python3 OS模块中的文件/目录方法说明十七

一. 简介 前面文章简单学习了 Python3 中 OS模块中的文件/目录的部分函数。 本文继续来学习 OS 模块中文件、目录的操作方法&#xff1a;os.walk() 方法、os.write()方法 二. Python3 OS模块中的文件/目录方法 1. os.walk() 方法 os.walk() 方法用于生成目录树中的文件名&a…

2025年2月2日(网络编程 tcp)

tcp 循环服务 import socketdef main():# 创建 socket# 绑定tcp_server socket.socket(socket.AF_INET, socket.SOCK_STREAM)tcp_server.bind(("", 8080))# socket 转变为被动tcp_server.listen(128)while True:# 产生专门为链接进来的客户端服务的 socketprint(&qu…

Rust 中的注释使用指南

Rust 中的注释使用指南 注释是代码中不可或缺的一部分&#xff0c;它帮助开发者理解代码的逻辑和意图。Rust 提供了多种注释方式&#xff0c;包括行注释、块注释和文档注释。本文将详细介绍这些注释的使用方法&#xff0c;并通过一个示例展示如何在实际代码中应用注释。 1. 行…

使用Pygame制作“青蛙过河”游戏

本篇博客将演示如何使用 Python Pygame 从零开始编写一款 Frogger 风格的小游戏。Frogger 是一款早期街机经典&#xff0c;玩家需要帮助青蛙穿越车水马龙的马路到达对岸。本示例提供了一个精简原型&#xff0c;包含角色移动、汽车生成与移动、碰撞检测、胜利条件等关键点。希望…

渗透测试过程中碰到的Symfony框架

0x01 不是很顺利的Nday利用 在一次渗透测试过程中发现了目标使用了Symfony框架&#xff0c;然后扫了下目录&#xff0c;发现存在app_dev.php 文件&#xff0c;尝试访问 发现开启了debug模式&#xff0c;Symfony 版本号为2.8.34 php版本5.6.40 也能查看phpinfo页面 然后在网上搜…

Games104——网络游戏的进阶架构

这里写目录标题 前言位移移动插值内插&#xff08;Interpolation&#xff09;外插&#xff08;Extrapolation&#xff09; 命中判定Hit Registration在客户端去判定 在服务器端去判定延迟补偿掩体问题躲进掩体走出掩体 技能前摇本地暴击效果 基础MMO框架分布式架构一致性哈希服…

2025年01月27日Github流行趋势

项目名称&#xff1a;onlook项目地址url&#xff1a;https://github.com/onlook-dev/onlook项目语言&#xff1a;TypeScript历史star数&#xff1a;5340今日star数&#xff1a;211项目维护者&#xff1a;Kitenite, drfarrell, iNerdStack, abhiroopc84, apps/dependabot项目简介…

【Redis】set 和 zset 类型的介绍和常用命令

1. set 1.1 介绍 set 类型和 list 不同的是&#xff0c;存储的元素是无序的&#xff0c;并且元素不允许重复&#xff0c;Redis 除了支持集合内的增删查改操作&#xff0c;还支持多个集合取交集&#xff0c;并集&#xff0c;差集 1.2 常用命令 命令 介绍 时间复杂度 sadd …

[SAP ABAP] 静态断点的使用

在 ABAP 编程环境中&#xff0c;静态断点通过关键字BREAK-POINT实现&#xff0c;当程序执行到这一语句时&#xff0c;会触发调试器中断程序的运行&#xff0c;允许开发人员检查当前状态并逐步跟踪后续代码逻辑 通常情况下&#xff0c;在代码的关键位置插入静态断点可以帮助开发…

从TinyZero的数据与源码来理解DeepSeek-R1-Zero的强化学习训练过程

1. 引入 TinyZero&#xff08;参考1&#xff09;是伯克利的博士生复现DeepSeek-R1-Zero的代码参仓库&#xff0c;他使用veRL来运行RL强化学习方法&#xff0c;对qwen2.5的0.5B、1.5B、3B等模型进行训练&#xff0c;在一个数字游戏数据集上&#xff0c;达到了较好的推理效果。 …

深度卷积神经网络实战无人机视角目标识别

本文采用深度卷积神经网络作为核心算法框架&#xff0c;结合PyQt5构建用户界面&#xff0c;使用Python3进行开发。YOLOv8以其高效的实时检测能力&#xff0c;在多个目标检测任务中展现出卓越性能。本研究针对无人机目标数据集进行训练和优化&#xff0c;该数据集包含丰富的无人…

初级数据结构:栈和队列

一、栈 (一)、栈的定义 栈是一种遵循后进先出&#xff08;LIFO&#xff0c;Last In First Out&#xff09;原则的数据结构。栈的主要操作包括入栈&#xff08;Push&#xff09;和出栈&#xff08;Pop&#xff09;。入栈操作是将元素添加到栈顶&#xff0c;这一过程中&#xf…