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

C++入门——“C++11-右值引用和移动语义”

        C++11相比于C++98增加以许多新特性,让C++语言更加灵活好用,但是貌似也增加了许多学习的难度,现在先看第一部分。

        一、右值引用和移动语义

        1.右值引用和左值引用

        在C++中,值可以大致分为右值和左值,左值大概是哪些已经被定义的变量或者对象,它一般具有持久性,它可以出现在赋值符号的左边,也可以出现在赋值符号的右边;右值一般指的是临时变量、字面常量等等。一般来说,区分左右值的方法可以用是否可以取地址来判断:左值可以取地址,右值不可以被取地址。

        下面让我们简单看一段代码吧:

void Func(int& a)
{cout << "左值引用" << endl;
}void Func(int&& a)
{cout << "右值引用" << endl;
}int main()
{int a = 1;Func(1);Func(a);return 0;
}

        其中,a是一个普通变量,为左值,而1是一个字面常量,为右值,那么最后的输出结果因该是“右值引用”、“左值引用”。让我们看一看结果:

       2.右值引用引用左值和左值引用引用右值

        右值可以被左值引用引用,因为右值一般具有常性,所以可以通过在左值引用前加上“const”来使得左值引用可以引用右值;而右值引用引用左值则需要使用“move”函数来改变左值的属性。

int main()
{int a = 2;const int& L = 1;int&& R = move(a);cout << L << endl << R << endl;return 0;
}

3.右值引用存在的意义

a.延长临时对象的生命周期

        右值引用可以延长临时对象的生命周期比如:

int main()
{string("123456789");return 0;
}

        此代码中,string的生命周期仅限于这一行,倘若使用右值引用来引用,那么就可以延长它的生命周期:

int main()
{string&& S = string("123456789");cout << S << endl;return 0;
}

        同样的,表达式相加的临时对象,函数返回时的临时对象,都可以使用右值引用来延长它的生命周期。

b.移动语义

        当然了,右值引用存在的意义可不是为了简单的延长生命周期,而是为了转移临时对象的数据,这使得数据的转移更加高效和安全,这也正是移动语义的的机制。在此之前还需要了解的是虽然右值是不可以被改变,但是右值引用是具有左值属性的,也就是说,被右值引用引用的右值是可以被修改的。

        拿拷贝构造来说,我们会将它的它的参数写为const类型的,在保证不修改实参的情况下,还能够接收右值。但是无论是左值还是右值,在有资源的情况下需要进行大量的复制行为,特别是在右值的情况下(因为右值的生命周期即将结束,还得进行一次复制,这样会造成效率的低下)。为了解决这个问题,这个时候就需要介绍一下移动构造和移动赋值了。

        移动构造和移动赋值旨在将临时对象(右值)的资源转移到我们的类中,由于右值的生命周期即将结束,秉承着趁你病要你命的原则,我是可以在虚弱的时候掠夺你的资源。这个时候我就可以直接把我的没用的东西和你的资源进行交换,这样做的效率可不是一般的高,因为避免了大量的数据拷贝。

        移动构造和移动赋值同普通的构造和赋值函数一样,只是参数变为了右值,并且函数内部进行资源的互换。

        就用我们的自己写的string类为例。(此处在linux环境下测试,因为VS的编译器会进行优化)

#pragma once
#include<iostream>using namespace std;namespace Mynamespace
{class string{friend ostream& operator<<(ostream& _cout, const Mynamespace::string& s);friend istream& operator>>(istream& _cin, Mynamespace::string& s);public:typedef char* iterator;public:string(const char* str = ""){_str = new char[strlen(str)+1];char* der = _str;const char* sour = str;while (*sour != '\0'){*(der++) = *(sour++);}*der = '\0';_size = strlen(str);_capacity = _size;}string(const string& s){_str = new char[s._capacity];char* der = _str;const char* sour = s._str;while (*sour != '\0'){*(der++) = *(sour++);}*der = '\0';_size = s._size;_capacity = s._capacity;}string(string&&  s){cout << "string(string&& s) 移动构造" << endl;swap(s);}~string(){delete[] _str;_capacity = 0;_size = 0;}string& operator=(const string& s){delete[] _str;_str = new char[strlen(s._str) + 1];char* der = _str;const char* sour = s._str;while (*sour != '\0'){*(der++) = *(sour++);}*der = '\0';_size = s._size;_capacity = s._capacity;return *this;}// 移动赋值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移动赋值" << endl;swap(s);return *this;}//// iteratortypedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}/// modifyvoid push_back(char c);string& operator+=(char c);void append(const char* str);string& operator+=(const char* str);void clear();void swap(string& s);const char* c_str()const;/// capacitysize_t size()const;size_t capacity()const;bool empty()const;void resize(size_t n, char c = '\0');void reserve(size_t n);/// accesschar& operator[](size_t index);const char& operator[](size_t index)const;///relational operatorsbool operator<(const string& s){return strcmp(_str, s._str) < 0;}bool operator<=(const string& s){return *this < s || *this == s;}bool operator>(const string& s){return !(*this <= s);}bool operator>=(const string& s){return !(*this < s);}bool operator==(const string& s){return strcmp(_str, s._str) == 0;}bool operator!=(const string& s){return !(*this == s);}// 返回c在string中第一次出现的位置size_t find(char c, size_t pos = 0) const;// 返回子串s在string中第一次出现的位置size_t find(const char* s, size_t pos = 0) const;// 在pos位置上插入字符c/字符串str,并返回该字符的位置string& insert(size_t pos, char c);string& insert(size_t pos, const char* str);// 删除pos位置上的元素,并返回该元素的下一个位置string& erase(size_t pos, size_t len);private:char* _str;size_t _capacity;size_t _size;static const size_t npos = -1;};
}
int main()
{Mynamespace::string mystring1 = Mynamespace::string("123456789");return 0;
}

        可能是编译器的版本的问题,在我的linux环境中,以上代码即使关闭了优化,也仍然进行直接构造而不是构造+移动构造:

cx@Ubuntu-Linux:~/study$ g++ test.cpp -fno-elide-constructors
cx@Ubuntu-Linux:~/study$ ./a.outstring(const char* str )

        有条件的小伙伴可以自己试一试,其中“-fno-elide-constructors”或者“-O0”可以关闭优化。

所以这里先测试一下移动赋值:

int main()
{Mynamespace::string mystring1;mystring1  = Mynamespace::string("123456789");return 0;
}
cx@Ubuntu-Linux:~/study$ g++ test.cpp -fno-elide-constructors
cx@Ubuntu-Linux:~/study$ ./a.outstring(const char* str )string(const char* str )
string& operator=(string&& s)

          可以看到最后调用了一次移动赋值。

二、引用折叠

        在C++11中,新增加了引用折叠的特性,引用折叠针对的是引用的引用,但是不可以显式的写出来引用的引用,比如:

int main()
{int a;int&& & b = a;return 0;
}

        但是可以隐式的引用(typedef后的引用):

int main()
{typedef int& L;typedef int&& R;int a;L& b = a;R& c = a;R&& d = 1;L&& e = a;return 0;
}

        引用折叠理解起来有点像与门,在这里,左值引用代表着0,右值引用代表着1,当左值引用右值引用同时存在的时候就是左值引用,只有当两个引用都为右值的时候才为右值引用。

        就拿以上代码为例    L& b = a; 里,b的类型为int&,    R& c = a; 里,c的类型为int&,R&& d = 1;  里,d 的类型为int&&,    L&& e = a; 里 e为左值。

        1.万能引用

        引用折叠的用途在哪里呢?实际上,它可以用来在函数模板中实现万能引用。请看以下函数模板:

template <class T>
void func(T&& n)
{cout << "void func(int&& n)" << endl;
}
int main()
{int a = 1;func(1);func(a);return 0;
}

        已知字面常量1是一个右值,变量a是一个左值,由于引用折叠,第一次调用的是右值引用版本的func,第二次是左值引用的func。最后的运行结果如下:

        这样可以用一个模板实现左值引用和右值引用的两个版本。

        2.完美转发

        先看以下代码:

void F(int& a)
{cout << "左值引用" << endl;}
void F(int&& a)
{cout << "右值引用" << endl;}
void F(const int& a)
{cout << "左值引用" << endl;
}template <class T>
void func(T&& n)
{F(n);
}int main()
{int a = 1;func(a);func(2);return 0;
}

        我们想要的输出结果是“左值引用”、“右值引用”,那么先看一下运行结果:

        哎。为什么是两个左值引用呢?

        由于右值引用的属性为左值,当我们想连续传递一个右值的时候,在第一次传递后,这个值就已经变为左值属性了,为了解决这个问题,C++11引入了完美转发,它的目得是为了在右值连续传递的过程中不改变右值的属性。

        完美转发的本质是一个函数模板forward,它的底层是一个强转(左值引用和右值引用的本质还是指针,只是在语义上不同)。它的用法是在传递参数的地方加上模板的类型,后边紧接着参数是传递的值:

void F(int& a)
{cout << "左值引用" << endl;}
void F(int&& a)
{cout << "右值引用" << endl;}
void F(const int& a)
{cout << "左值引用" << endl;
}template <class T>
void func(T&& n)
{F(forward<T>(n));
}int main()
{int a = 1;func(a);func(2);return 0;
}

        运行结果:

        我们可以分析一下:

        a.传递的参数为a的时候,a为左值,func发生了引用折叠,编译器推导出T为int&,那么forward强转n为左值并返回。

        b.传递的参数为字面常量1的时候,1为右值,func没有发生引用折叠,编译器推导出T为int&&,那么forward强转n为右值并返回。

        总的来说,完美转发就是根据T的类型来推导最后返回的类型,如果T是右值,那么最后返回的就是右值属性的对象,如果T为左值,那么最后返回的就是左值属性的对象。

相关文章:

C++入门——“C++11-右值引用和移动语义”

C11相比于C98增加以许多新特性&#xff0c;让C语言更加灵活好用&#xff0c;但是貌似也增加了许多学习的难度&#xff0c;现在先看第一部分。 一、右值引用和移动语义 1.右值引用和左值引用 在C中&#xff0c;值可以大致分为右值和左值&#xff0c;左值大概是哪些已经被定义的变…...

timm使用笔记

timm&#xff08;Timm is a model repository for PyTorch&#xff09;是一个 PyTorch 原生实现的计算机视觉模型库。它提供了预训练模型和各种网络组件&#xff0c;可以用于各种计算机视觉任务&#xff0c;例如图像分类、物体检测、语义分割等等。timm&#xff08;库提供了预训…...

android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址

Android 浏览器是一种运行在Android操作系统上的应用程序&#xff0c;主要用于访问和查看互联网内容。以下是关于Android浏览器的详细介绍&#xff1a; 1. 基本功能 Android浏览器提供了用户浏览网页的基本功能&#xff0c;如&#xff1a; 网页加载&#xff1a;支持加载静态…...

贪心算法入门(一)

1.什么是贪心算法&#xff1f; 贪心算法是一种解决问题的策略&#xff0c;它将复杂的问题分解为若干个步骤&#xff0c;并在每一步都选择当前最优的解决方案&#xff0c;最终希望能得到全局最优解。这种策略的核心在于“最优”二字&#xff0c;意味着我们追求的是以最少的时间和…...

C# ref和out 有什么区别,分别用在那种场景

在C#中&#xff0c;ref和out都是用于按引用传递参数的关键字&#xff0c;但它们有一些细微的差别和使用场景。 ref 关键字 ref 关键字用于按引用传递参数。这意味着当你将一个变量作为参数传递给一个方法时&#xff0c;你不是传递变量的值&#xff0c;而是传递变量的引用。因…...

TikTok直播专线:提升直播效果和体验

作为当今全球最受欢迎的社交媒体平台之一&#xff0c;TikTok为商家提供了无限的商机和市场。然而&#xff0c;商家在使用TikTok时也面临着许多挑战&#xff0c;如网络延迟、直播中断以及账号被封等问题。TikTok直播专线旨在为商家提供高速稳定的网络连接&#xff0c;助力他们在…...

由浅入深逐步理解spring boot中如何实现websocket

实现websocket的方式 1.springboot中有两种方式实现websocket&#xff0c;一种是基于原生的基于注解的websocket&#xff0c;另一种是基于spring封装后的WebSocketHandler 基于原生注解实现websocket 1&#xff09;先引入websocket的starter坐标 <dependency><grou…...

1-petalinux 问题记录-根文件系统分区问题

在MPSOC上使用SD第二分区配置根文件系统的时候&#xff0c;需要选择对应的bootargs&#xff0c;但是板子上有emmc和sd两个区域&#xff0c;至于配置哪一种mmcblk0就出现了问题&#xff0c;从vivado中的BlockDesign和MLK XCZU2CG原理图来看的话&#xff0c;我使用的SD卡应该属于…...

微信小程序的上拉刷新与下拉刷新

效果图如下&#xff1a; 上拉刷新 与 下拉刷新 代码如下&#xff1a; joked.wxml <scroll-view class"scroll" scroll-y refresher-enabled refresher-default-style"white" bindrefresherrefresh"onRefresh" refresher-triggered&qu…...

【大语言模型】ACL2024论文-05 GenTranslate: 大型语言模型是生成性多语种语音和机器翻译器

【大语言模型】ACL2024论文-05 GenTranslate: 大型语言模型是生成性多语种语音和机器翻译器 GenTranslate: 大型语言模型是生成性多语种语音和机器翻译器 目录 文章目录 【大语言模型】ACL2024论文-05 GenTranslate: 大型语言模型是生成性多语种语音和机器翻译器目录摘要研究背…...

KPRCB结构之ReadySummary和DispatcherReadyListHead

ReadySummary: Uint4B DispatcherReadyListHead : [32] _LIST_ENTRY 请参考 _KTHREAD *__fastcall KiSelectReadyThread(ULONG LowPriority, _KPRCB *Prcb)...

批处理之for语句从入门到精通--呕血整理

文章目录 一、前言二、for语句的基本用法三、文本解析显神威&#xff1a;for /f 用法详解四、翻箱倒柜遍历文件夹&#xff1a;for /r五、仅仅为了匹配第一层目录而存在&#xff1a;for /d六、计数循环&#xff1a;for /l后记 for语句从入门到精通 一、前言 在批处理中&#…...

pycharm小游戏贪吃蛇及pygame模块学习()

由于代码量大&#xff0c;会逐渐发布 一.pycharm学习 在PyCharm中使用Pygame插入音乐和图片时&#xff0c;有以下这些注意事项&#xff1a; 插入音乐&#xff1a; - 文件格式支持&#xff1a;Pygame常用的音乐格式如MP3、OGG等&#xff0c;但MP3可能需额外安装库&#xf…...

redis实战--黑马商城 记录

一、视频地址 黑马程序员Redis入门到实战教程&#xff0c;深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目 二、笔记地址 Redis基础篇Redis实战篇...

机器人技术革新:人工智能的强力驱动

内容概要 在当今世界&#xff0c;机器人技术与人工智能的结合正如星星与大海&#xff0c;彼此辉映。随着科技的不断进步&#xff0c;人工智能不仅仅是为机器人赋予了“聪明的大脑”&#xff0c;更是推动了整个行业的快速发展。回顾机器人技术的发展历程&#xff0c;我们会发现…...

漫途焊机安全生产监管方案,提升安全生产管理水平!

随着智能制造时代的到来&#xff0c;企业安全生产管理的重要性日益凸显。特别是在现代工厂中&#xff0c;焊机的安全生产监管成为了一个不容忽视的重要环节。传统的焊机安全生产监管方式存在诸多不足&#xff0c;如人工巡检频率低、数据延迟、安全隐患发现不及时等问题。因此&a…...

动态规划之两个数组的 dp(上)

文章目录 最长公共子序列不相交的线不同的子序列通配符匹配 最长公共子序列 题目&#xff1a;最长公共子序列 思路 选取s1的[0, i]区间以及s2的[0&#xff0c; j]区间作为研究对象 状态表示&#xff1a;dp[i][j]表示&#xff0c;s1的[0, i]区间以及s2的[0&#xff0c; j]区间内…...

DC-9靶机通关

这是这个系列的最后一个靶机了&#xff01;&#xff01;&#xff01;经过前面的锻炼和学习&#xff0c;这次我的目标是尽量不借助任何教程或者提示来拿下这个靶机&#xff01;&#xff01;&#xff01;下面我们看能不能成功&#xff01;&#xff01;&#xff01; 1.实验环境 攻…...

前端注释都应该怎么写?

以下是一些前端注释的例子&#xff0c;展示了如何应用前面提到的建议&#xff1a; 1. 使用清晰、简洁的语言 // 计算两个数的平均值 function calculateAverage(a, b) {return (a b) / 2; }2. 描述代码的目的和功能 // 将日期格式化为 "YYYY-MM-DD" 的字符串 fun…...

深入解析缓存模式下的数据一致性问题

今天&#xff0c;我们来聊聊常见的缓存模式和数据一致性问题。 常见的缓存模式有&#xff1a;Cache Aside、Read Through、Write Through、Write Back、Refresh Ahead、Singleflight。 缓存模式 Cache Aside 在 Cache Aside 模式中&#xff0c;是把缓存当做一个独立的数据源…...

Pixel Aurora Engine真实案例:用‘蒸汽朋克猫武士’生成整套游戏美术资源

Pixel Aurora Engine真实案例&#xff1a;用蒸汽朋克猫武士生成整套游戏美术资源 1. 项目背景与工具介绍 Pixel Aurora Engine&#xff08;像素极光引擎&#xff09;是一款基于AI扩散模型的高端像素艺术生成工具。它采用复古的8-bit游戏机风格界面&#xff0c;却能产出专业级…...

告别水印烦恼!3步轻松去水印,新手秒上手。

找到心仪的图片有水印、做设计好不容易找到的素材有水印、下载好看的壁纸有水印&#xff0c;遇到的好图全被水印扫兴&#xff1f;PS去水印&#xff0c;操作复杂&#xff0c;学习成本高&#xff0c;浪费时间&#xff1b;用专业去水印工具&#xff0c;收费昂贵&#xff0c;还有广…...

别再用默认字典了!DVWA暴力破解实战:从Low到High,手把手教你配置Burp Suite的Pitchfork模式

别再用默认字典了&#xff01;DVWA暴力破解实战&#xff1a;从Low到High&#xff0c;手把手教你配置Burp Suite的Pitchfork模式 在渗透测试的入门阶段&#xff0c;暴力破解往往是最先接触的攻击手段之一。但许多新手在DVWA的High级别面前束手无策——那些看似简单的登录表单&am…...

嵌入式开发必备:三大代码对比工具深度评测

1. 代码对比工具概述作为一名嵌入式开发工程师&#xff0c;我每天都要处理大量的代码修改和版本对比工作。在多年的开发实践中&#xff0c;我发现选择合适的代码对比工具能极大提升工作效率。虽然Beyond Compare是业内公认的标杆产品&#xff0c;但实际工作中我们还有更多选择&…...

Qwen3-VL-30B部署避坑指南:从下载到运行一气呵成

Qwen3-VL-30B部署避坑指南&#xff1a;从下载到运行一气呵成 1. 为什么选择Qwen3-VL-30B Qwen3-VL-30B是目前通义千问系列中最强大的视觉-语言模型&#xff0c;它在多个方面实现了显著提升&#xff1a; 更优秀的文本理解和生成&#xff1a;能够处理复杂语义和长文本更深入的…...

BP算法在SAR成像中的高效实现与优化策略

1. BP算法在SAR成像中的核心原理 BP&#xff08;Back Projection&#xff09;算法是合成孔径雷达&#xff08;SAR&#xff09;成像中最直观的时域处理方法。我第一次接触这个算法时&#xff0c;就被它那种"暴力美学"式的计算逻辑震撼到了——它不需要任何傅里叶变换的…...

MATLAB xyz2stl实战:手把手教你修复GitHub热门工具包的常见报错(含stlWrite函数缺失解决方案)

MATLAB xyz2stl实战&#xff1a;从报错排查到完整工作流搭建 当你从GitHub下载了NWRichmond/xyz2stl工具包&#xff0c;满心期待地运行却看到"未定义函数或变量stlWrite"的红色报错时&#xff0c;这种挫败感我深有体会。作为MATLAB社区中下载量排名前10%的三维数据处…...

魔兽世界插件开发5分钟速成:从零掌握API查询与宏命令管理终极指南

魔兽世界插件开发5分钟速成&#xff1a;从零掌握API查询与宏命令管理终极指南 【免费下载链接】wow_api Documents of wow API -- 魔兽世界API资料以及宏工具 项目地址: https://gitcode.com/gh_mirrors/wo/wow_api 魔兽世界API文档平台与宏工具是一个专为《魔兽世界》玩…...

在QT中将多个项目(同代码不同ui和资源文件)合并

Linux下的qt环境 我现在有三个项目&#xff0c;代码一模一样&#xff0c;只有UI文件和资源文件不同现在想要合并代码 后期好上传在git 仅需要一个分支 更好管理将随行 康养 采图三个项目代码合并 思路是这样的 将每个项目都分类打包区分开我是在康养这个项目的基础上合…...

4步精通开源SMU调试工具:AMD Ryzen处理器深度配置与性能调优全攻略

4步精通开源SMU调试工具&#xff1a;AMD Ryzen处理器深度配置与性能调优全攻略 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址…...