当前位置: 首页 > 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;是把缓存当做一个独立的数据源…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

质量体系的重要

质量体系是为确保产品、服务或过程质量满足规定要求&#xff0c;由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面&#xff1a; &#x1f3db;️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限&#xff0c;形成层级清晰的管理网络&#xf…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...