【C++11】移动语义

回顾

const int c的c是可以被取地址的,尽管是常量。所以以是否为常量来判断是否为右值是错误的。

左值与右值正确的区分方法是是否能够被取地址。(能被取地址也就代表着是一个持久状态,即有持久的存储空间的值)

常见的左值有我们定义的变量、对象,或者解引用表达式和传引用返回。(比如`string s(“2077”);然后s[0]就是一个传引用返回值,也就是一个左值)

常见的右值有常量、(表达式求值过程中创建的)临时对象、匿名对象。

左值引用和右值引用可以交叉引用但是有条件:

  • const左值引用可以引用右值

  • 右值引用可以引用move(左值)

    (move是库里面的一个函数模版,本质内部是进行强制类型转换,涉及一些引用折叠的知识)

类型是我们对内存里一块空间的定义,而C++是可以对类型进行转换的,也就是对内存空间不同的解释。

类型决定了语法意义上我们怎么对这块空间的数据进行处理。
拿链表指针和迭代器举个例子:
在这里插入图片描述
这里ptr和it同样都是4个字节存放地址,但是却解释为不同的类型,意义就不同了、
所以我们知道,内存存的是数据,但只是存储数据本身没什么意义,而将数据解释为不同的类型,意义就千变万化了。

3.延长生命周期

这一般指的是临时对象、匿名对象。因为它们一般生命周期只在当前行,而延长后可以与整个域生命周期一样长。但将其从一个栈帧延长到另一个栈帧,是做不到的。
在这里插入图片描述
比如这个str的生命周期不可能延长到main栈帧里去
因为延长生命周期没有改变它的存储位置
在这里插入图片描述
在这里,main函数里调用了函数Func1,调用结束后Func1的栈帧是要回收的,下次调用Func2还占用的是这块空间,所以怎么可能将Func1中的str单独延长生命周期呢?
所以要搞清楚延长生命周期的对象是指的谁。
延长生命周期没有改变存储位置。
再看一例:
在这里插入图片描述
这里创建一个匿名对象,我们可以看到它在下一句代码之前就析构了,说明匿名对象的生命周期只在这一行。

那么现在使用const左值引用对其生命周期进行延长:
在这里插入图片描述

可以看到延长之后它的生命周期就跟着引用走了
在这里插入图片描述

注意:在这里插入图片描述
右值引用延长生命周期效果同样如此。

4.编译器对拷贝的优化之所以复杂,有两方面的因素:一方面要支持c++委员会制定的语法新规则,另一方面要为了c++的高效适当进行优化。
左值引用和右值引用最终目的都是减少拷贝提高效率(左值引用还有其他使用场景比如输出型参数,修改参数或返回值)

(补充)输出型参数:

在C++中,输出型参数通常通过指针或引用来实现,因为函数参数默认是按值传递的,直接传递普通变量无法修改外部变量的值。以下是C++中输出型参数的实现方式:

1. 使用指针作为输出型参数

通过传递指针,函数可以修改指针所指向的内存地址中的值。

#include <iostream>
using namespace std;// 函数定义,使用指针作为输出型参数
void calculate(int a, int b, int* sum, int* product) {*sum = a + b;       // 修改sum指向的值*product = a * b;   // 修改product指向的值
}int main() {int x = 5, y = 10;int sumResult, productResult;// 传递变量的地址calculate(x, y, &sumResult, &productResult);cout << "Sum: " << sumResult << endl;         // 输出:Sum: 15cout << "Product: " << productResult << endl; // 输出:Product: 50return 0;
}

说明:

int* sum 和 int* product 是指针参数,用于接收外部变量的地址。

在函数内部通过 *sum 和 *product 修改外部变量的值。

2. 使用引用作为输出型参数

引用是C++中更安全和直观的方式,可以直接操作外部变量。

#include <iostream>
using namespace std;// 函数定义,使用引用作为输出型参数
void calculate(int a, int b, int& sum, int& product) {sum = a + b;       // 直接修改sum引用的值product = a * b;   // 直接修改product引用的值
}int main() {int x = 5, y = 10;int sumResult, productResult;// 传递变量的引用calculate(x, y, sumResult, productResult);cout << "Sum: " << sumResult << endl;         // 输出:Sum: 15cout << "Product: " << productResult << endl; // 输出:Product: 50return 0;
}

说明:

int& sum 和 int& product 是引用参数,直接绑定到外部变量。

在函数内部可以直接操作 sum 和 product,无需解引用。

3.指针和引用的对比

在这里插入图片描述

4. 使用输出型参数的场景!

  • 需要从函数中返回多个值。

  • 需要修改传入的参数值。

  • 避免返回大型对象(通过引用或指针传递,避免拷贝开销)。

5. 示例:返回多个值

以下是一个返回多个值的示例,使用引用作为输出型参数:

#include <iostream>
#include <tuple> // 如果需要返回多个值,也可以使用std::tuple
using namespace std;void getResults(int a, int b, int& sum, int& diff, int& product) {sum = a + b;diff = a - b;product = a * b;
}int main() {int x = 10, y = 4;int sum, diff, product;getResults(x, y, sum, diff, product);cout << "Sum: " << sum << endl;         // 输出:Sum: 14cout << "Difference: " << diff << endl; // 输出:Difference: 6cout << "Product: " << product << endl; // 输出:Product: 40return 0;
}

5.左值引用的不足:
部分函数返回场景,只能传值返回,不能左值引用返回。
当前函数的局部对象,出了当前函数的作用域生命周期到了销毁了不能左值引用返回,只能传值返回。

class Solution 
{
public:// 这⾥的传值返回拷⻉代价就太⼤了 vector<vector<int>> generate(int numRows) {vector<vector<int>> vv(numRows);for(int i = 0; i < numRows; ++i){vv[i].resize(i+1, 1);}for(int i = 2; i < numRows; ++i){for(int j = 1; j < i; ++j){vv[i][j] = vv[i-1][j] + vv[i-1][j-1];}}return vv;}
};
int main()
{vector<vector<int>> ret = Solution().generate(100);return 0;
}

不优化的情况下编译器还要拷贝两次:
在这里插入图片描述
如果new的话,忘记释放可能会导致内存泄漏。

一种较老的比较好的解决方式(输出型参数):
class Solution 
{
public:void generate(int numRows,vector<vector<int>>& vv) {for(int i = 0; i < numRows; ++i){vv[i].resize(i+1, 1);}for(int i = 2; i < numRows; ++i){for(int j = 1; j < i; ++j){vv[i][j] = vv[i-1][j] + vv[i-1][j-1];}}}
};int main()
{vector<vector<int>> ret;Solution().generate(100,ret);return 0;
}

但这样写,用的角度,多多少少很别扭。

因为c++委员会更新标准较晚,编译器的设计者选择先从编译器的角度进行优化。

编译器的第一轮优化:“跳过中间商”
在这里插入图片描述
(从左边这样到右边这样)

namespace bit
{ string addStrings(string num1, string num2){string str;int end1 = num1.size() - 1, end2 = num2.size() - 1;int next = 0;while (end1 >= 0 || end2 >= 0){int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;int ret = val1 + val2 + next;next = ret / 10;ret = ret % 10;str += ('0' + ret);}if (next == 1)str += '1';reverse(str.begin(), str.end());cout << "******************************" << endl;return str;}
}// 场景1 
int main()
{bit::string ret = bit::addStrings("11111", "2222");cout << ret.c_str() << endl;return 0;
}

把优化全关掉的场景:
在这里插入图片描述
这里前两组的构造+拷贝构造是"11111"与 "2222"的,最后的两个拷贝构造一次是str去拷贝构造临时对象,一次是临时对象去拷贝构造ret。

VS2019下的优化,合二为一:
在这里插入图片描述
在这里插入图片描述

可以看到参数的构造与拷贝构造合二为一了,返回值的两次拷贝构造也合二为一。
在这里插入图片描述
2代优化非常恐怖:
在这里插入图片描述
可以看到是从构造+拷贝构造+拷贝构造到1代的构造+拷贝构造,再到2代的构造

在这里插入图片描述

在这里插入图片描述

最后变成了,干脆不创建str了,直接创建ret,让str变成ret的别名。
这种优化的思路很像上面说的输出型参数:

vector<vector<int>> ret;
Solution().generate(100,ret);

(编译器优化后,右值引用没有意义了吗?)
答:优化是有限度的,能解决一些问题,但是一些问题也解决不了。

比如在这个场景中:
在这里插入图片描述
现在不是构造+两次拷贝构造而是构造+一次拷贝构造+一次拷贝赋值的场景
彻底不优化是这样的:
在这里插入图片描述
一代优化:
在这里插入图片描述
可以看到,传参是合二为一优化了,但是拷贝构造+拷贝赋值没有优化
2代优化:
在这里插入图片描述

2代优化是去掉了拷贝构造,本质是让构造和拷贝构造合二为一成一次构造了。相当于一上来就构造了临时对象,让str是临时对象的别名。
编译器优化也是有限度的,优化终止于此了。
C++11出来后这个程序是如何解决的?
如果编译器彻底不优化,是这样的:
在这里插入图片描述
即使这样这个效率也高,因为移动构造只是抢夺资源,不会拷贝,代价极低。
……
总结就是C++11之前很依赖编译器的优化,有了移动拷贝和赋值之后,对编译器的优化的依赖很小,只是锦上添花不再是雪中送炭。
一个问题:每个类在C++11后都要写移动构造移动赋值吗?
深拷贝的自定义类型(如string、vector、map…)写才有价值。
而对于浅拷贝的类型来说,编译器的优化小赚一笔,所以移动语义+编译器优化是很无敌的存在。

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

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

相关文章

LangChain教程 - Agent -之 ZERO_SHOT_REACT_DESCRIPTION

在构建智能 AI 助手时&#xff0c;我们希望模型能够智能地调用工具&#xff0c;以便提供准确的信息。LangChain 提供了 AgentType.ZERO_SHOT_REACT_DESCRIPTION&#xff0c;它结合了 ReAct&#xff08;Reasoning Acting&#xff09;策略&#xff0c;使得 LLM 可以基于工具的描…

移动Android和IOS自动化中常见问题

APP测试逻辑 在app编写自动化测试用例时&#xff0c;通常会出现只是简单的点点点过程&#xff0c;然而却忽略了在实际的自动化实现过程中&#xff0c;软件是对app元素的判断来执行测试脚本。所以会出现在后期已经写好自动化脚本之后还会对测试用例的更新。 App在测试时&#…

python高效试用17---两个字符串组成一个新的字符串和两个字符串组成元组作为key哪个更高效

在 Python 中&#xff0c;使用字符串连接 (str1 str2) 作为 key 和使用元组 ((str1, str2)) 作为 key 的效率差异&#xff0c;主要受以下因素影响&#xff1a; 哈希计算速度&#xff1a; 字符串连接 (str1 str2)&#xff1a;会创建一个新的字符串对象&#xff0c;并计算哈希…

深入浅出Java try-with-resources:告别资源泄漏的烦恼

一、为什么需要try-with-resources&#xff1f; 在Java开发中&#xff0c;我们经常需要处理各种资源&#xff1a;文件流、数据库连接、网络套接字等。这些资源都有一个共同特点——必须在使用后正确关闭。传统的资源管理方式存在三大痛点&#xff1a; 代码臃肿&#xff1a;每…

Python+DeepSeek:开启AI编程新次元——从自动化到智能创造的实战指南

文章核心价值 技术热点:结合全球最流行的编程语言与国产顶尖AI模型实用场景:覆盖代码开发/数据分析/办公自动化等高频需求流量密码:揭秘大模型在编程中的创造性应用目录结构 环境搭建:5分钟快速接入DeepSeek场景一:AI辅助代码开发(智能补全+调试)场景二:数据分析超级助…

Linux tcpdump -any抓的包转换成标准的pcap

在 Linux 中使用 tcpdump -any 抓包并转换为标准 pcap 文件时出现额外字段,通常与 链路层协议头部的差异 以及 pcap 文件格式的兼容性 有关。以下是详细原因和解决方案: 一、问题原因分析 -any 选项的局限性 tcpdump -any 会自动猜测链路层协议类型(如 Ethernet、IEEE 802…

【SpringMVC】深入解析使用 Postman 在请求中传递对象类型、数组类型、参数类型的参数方法和后端参数重命名、及非必传参数设置的方法

SpringMVC—请求传参 1. 传递对象 如果参数比较多时&#xff0c;方法声明就需要有很多形参&#xff1b;并且后续每次新增一个参数&#xff0c;也需要修改方法声明. 我们不妨把这些参数封装为一个对象&#xff1b; Spring MVC 也可以自动实现对象参数的赋值&#xff0c;比如 Us…

一个差劲的软件设计

项目概况&#xff1a; 之前自己设计并开发了一个用C#开发的上位机软件&#xff0c;整个软件只有一个Form&#xff0c;一个TabControl&#xff0c;3个TabControlPanel&#xff0c;总共100多个lable、textbox、ListBox等控件都放在这3个TabControlPanel里。 问题&#xff1a; 1.…

Linux练级宝典->进程控制详解(进程替换,fork函数)

目录 进程创建 fork函数 写时拷贝 进程终止 进程退出码 exit函数 _exit函数 return&#xff0c;exit _exit之间的区别和联系 进程等待 进程等待的必要性 获取子进程status 进程等待的方法 wait waipid 多子进程创建理解 非阻塞轮询检测子进程 进程程序替换 替…

RabbitMq--消息可靠性

12.消息可靠性 1.消息丢失的情况 生产者向消息代理传递消息的过程中&#xff0c;消息丢失了消息代理&#xff08; RabbitMQ &#xff09;把消息弄丢了消费者把消息弄丢了 那怎么保证消息的可靠性呢&#xff0c;我们可以从消息丢失的情况入手——从生产者、消息代理&#xff0…

Windows中在VSCode/Cursor上通过CMake或launch文件配置CUDA编程环境

前置步骤 安装符合GPU型号的CUDA Toolkit 配置好 nvcc 环境变量 安装 Visual Studio 参考https://blog.csdn.net/Cony_14/article/details/137510909 VSCode 安装插件 Nsight Visual Studio Code Edition 注意&#xff1a;不是vscode-cudacpp。若两个插件同时安装&#xff0c;…

Spark(8)配置Hadoop集群环境-使用脚本命令实现集群文件同步

一.hadoop的运行模式 二.scp命令————基本使用 三.scp命令———拓展使用 四.rsync远程同步 五.xsync脚本集群之间的同步 一.hadoop的运行模式 hadoop一共有如下三种运行方式&#xff1a; 1. 本地运行。数据存储在linux本地&#xff0c;测试偶尔用一下。我们上一节课使用…

聚焦两会:科技与发展并进,赛逸展2025成创新新舞台

在十四届全国人大三次会议和全国政协十四届三次会议期间&#xff0c;代表委员们围绕多个关键议题展开深入讨论&#xff0c;为国家未来发展谋篇布局。其中&#xff0c;技术竞争加剧与经济转型需求成为两会焦点&#xff0c;将在首都北京举办的2025第七届亚洲消费电子技术贸易展&a…

【音视频】ffmpeg命令提取像素格式

1、提取YUV数据 提取yuv数据&#xff0c;并保持分辨率与原视频一致 使用-pix_fmt或-pixel_format指定yuv格式提取数据&#xff0c;并保持原来的分辨率 ffmpeg -i music.mp4 -t "01:00" -pixel_format yuv420p music.yuv提取成功后&#xff0c;可以使用ffplay指定y…

【从零开始学习计算机科学】计算机体系结构(二)指令级并行(ILP)

【从零开始学习计算机科学】【从零开始学习计算机科学】计算机体系结构(二)指令级并行(ILP) ILP流水线(pipeline)流水线调度循环展开和循环流水循环展开。循环展开的具体步骤可以描述为,软件流水(循环流水)。我们可以通过流水线的思想处理循环的执行,即不需要这一次的…

android edittext 防止输入多个小数点或负号

有些英文系统的输入法,或者定制输入法。使用xml限制不了输入多个小数点和多个负号。所以代码来控制。 一、通过XML设置限制 <EditTextandroid:id="@+id/editTextNumber"android:layout_width="wrap_content"android:layout_height="wrap_conten…

2019年蓝桥杯第十届CC++大学B组真题及代码

目录 1A&#xff1a;组队&#xff08;填空5分_手算&#xff09; 2B&#xff1a;年号字符&#xff08;填空5分_进制&#xff09; 3C&#xff1a;数列求值&#xff08;填空10分_枚举&#xff09; 4D&#xff1a;数的分解&#xff08;填空10分&#xff09; 5E&#xff1a;迷宫…

从C#中的MemberwiseClone()浅拷贝说起

MemberwiseClone() 是 C# 中的一个方法&#xff0c;用于创建当前对象的浅拷贝&#xff08;shallow copy&#xff09;。它属于 System.Object 类&#xff0c;因此所有 C# 对象都可以调用该方法。 1. MemberwiseClone() 的含义 浅拷贝&#xff1a;MemberwiseClone() 会创建一个新…

笔记六:单链表链表介绍与模拟实现

在他一生中&#xff0c;从来没有人能够像你们这样&#xff0c;以他的视角看待这个世界。 ---------《寻找天堂》 目录 文章目录 一、什么是链表&#xff1f; 二、为什么要使用链表&#xff1f; 三、 单链表介绍与使用 3.1 单链表 3.1.1 创建单链表节点 3.1.2 单链表的头插、…

尚硅谷爬虫note15n

1. 多条管道 多条管道开启&#xff08;2步&#xff09;&#xff1a; (1)定义管道类 &#xff08;2&#xff09;在settings中开启管道 在pipelines中&#xff1a; import urllib.request # 多条管道开启 #(1)定义管道类 #&#xff08;2&#xff09;在setti…