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

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

Go语言多线程问题

打印零与奇偶数&#xff08;leetcode 1116&#xff09; 方法1&#xff1a;使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...

Webpack性能优化:构建速度与体积优化策略

一、构建速度优化 1、​​升级Webpack和Node.js​​ ​​优化效果​​&#xff1a;Webpack 4比Webpack 3构建时间降低60%-98%。​​原因​​&#xff1a; V8引擎优化&#xff08;for of替代forEach、Map/Set替代Object&#xff09;。默认使用更快的md4哈希算法。AST直接从Loa…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏

一、引言 在深度学习中&#xff0c;我们训练出的神经网络往往非常庞大&#xff08;比如像 ResNet、YOLOv8、Vision Transformer&#xff09;&#xff0c;虽然精度很高&#xff0c;但“太重”了&#xff0c;运行起来很慢&#xff0c;占用内存大&#xff0c;不适合部署到手机、摄…...

Xela矩阵三轴触觉传感器的工作原理解析与应用场景

Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知&#xff0c;帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量&#xff0c;能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度&#xff0c;还为机器人、医疗设备和制造业的智…...