C++左值右值、左值引用右值引用、移动语义move

news/2024/5/17 15:20:21/文章来源:https://blog.csdn.net/kakaka666/article/details/126909921

目录

1.什么是左值、右值

2.什么是左值引用&、右值引用&&

2.1左值引用&

2.2右值引用&&

2.3对左右值引用本质的讨论 

2.3.1右值引用有办法指向左值吗? 

2.3.2左值引用、右值引用本身是左值还是右值? 

2.4 右值引用使用场景

2.4.1浅拷贝重复释放

2.4.2深拷贝构造函数

2.4.3移动构造函数 

2.5移动(move )语义 

 Demo

2.6 forward 完美转发 

2.7 emplace_back 减少内存拷贝和移动


1.什么是左值、右值

可以从2个角度判断:

  1. 左值可以取地址、位于等号左边;
  2. 而右值没法取地址,位于等号右边。

int a = 6;

  1. a可以通过 & 取地址,位于等号左边,所以a是左值。
  2. 6位于等号右边,6没法通过 & 取地址,所以6是个右值。

再举个复杂点的例子:

struct A {A(int a = 0) {a_ = a;}int a_;};A a = A();
  1. 同样的,a可以通过 & 取地址,位于等号左边,所以a是左值。
  2. A()是个临时值,没法通过 & 取地址,位于等号右边,所以A()是个右值。

可见左右值的概念很清晰,有地址的变量就是左值,没有地址的字面值、临时值就是右值。

2.什么是左值引用&、右值引用&&

引用本质是别名,可以通过引用修改变量的值,传参时传引用可以避免拷贝。

2.1左值引用&

左值引用:能指向左值,不能指向右值的就是左值引用:

int a = 5;int &ref_a = a; // 左值引用指向左值,编译通过int &ref_a = 5; // 左值引用指向了右值,会编译失败

引用是变量的别名,由于右值没有地址,没法被修改,所以左值引用无法指向右值。

但是,const左值引用是可以指向右值的:

const int &ref_a = 5; // 编译通过

const左值引用不会修改指向值,因此可以指向右值,这也是为什么要使用 const & 作为函数参数的原因之一,如 std::vector 的 push_back :

void push_back (const value_type& val);

如果没有 const , vec.push_back(5) 这样的代码就无法编译通过。

2.2右值引用&&

再看下右值引用,右值引用的标志是 && ,顾名思义,右值引用专门为右值而生,可以指向右值,不能指向左值:

int &&ref_a_right = 5; // okint a = 5;int &&ref_a_left = a; // 编译不过,右值引用不可以指向左值ref_a_right = 6; // 右值引用的用途:可以修改右值

        右值引用就是对一个右值进行引用的类型。因为右值没有名字,所以我们只能通过引用的方式找到它。无论声明左值引用还是右值引用都必须立即进行初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名。

2.3对左右值引用本质的讨论 

2.3.1右值引用有办法指向左值吗? 

有办法,使用 std::move :

int a = 5; // a是个左值int &ref_a_left = a; // 左值引用指向左值int &&ref_a_right = std::move(a); // 通过std::move将左值转化为右值,可以被右值引用指向cout << a; // 打印结果:5

在上边的代码里,看上去是左值a通过std::move移动到了右值ref_a_right中,那是不是a里边就没有值了?

并不是,打印出a的值仍然是5。


std::move 是一个非常有迷惑性的函数:
1.不理解左右值概念的人们往往以为它能把一个变量里的内容移动到另一个变量;
2.但事实上std::move移动不了什么,唯一的功能是把左值强制转化为右值,让右值引用可以指向左值。其实现等同于一个类型转换: static_cast<T&&>(lvalue) 。 所以,单纯的std::move(xxx)不会有性能提升


同样的,右值引用能指向右值,本质上也是把右值提升为一个左值,并定义一个右值引用通过std::move指向该左值: 

int &&ref_a = 5;ref_a = 6;//等同于以下代码:int temp = 5;int &&ref_a = std::move(temp);ref_a = 6;

2.3.2左值引用、右值引用本身是左值还是右值? 

被声明出来的左、右值引用都是左值。 因为被声明出的左右值引用是有地址的,也位于等号左边。仔细看下边代码: 

#include <iostream>using namespace std;void change(int&& right_value) {right_value = 8;}int main() {int a = 5; // a是个左值int &ref_a_left = a; // ref_a_left是个左值引用int &&ref_a_right = std::move(a); // ref_a_right是个右值引用change(a); // 编译不过,a是左值,change参数要求右值change(ref_a_left); // 编译不过,左值引用ref_a_left本身也是个左值change(ref_a_right); // 编译不过,右值引用ref_a_right本身也是个左值change(std::move(a)); // 编译通过change(std::move(ref_a_right)); // 编译通过change(std::move(ref_a_left)); // 编译通过change(5); // 当然可以直接接右值,编译通过// 打印这三个左值的地址,都是一样的cout << &a << ' ';cout << &ref_a_left << ' ';cout << &ref_a_right;}

        看完后你可能有个问题,std::move会返回一个右值引用 int && ,它是左值还是右值呢? 从表达式 int&&ref = std::move(a) 来看,右值引用 ref 指向的必须是右值,所以move返回的 int && 是个右值。
        所以右值引用既可能是左值,又可能是右值吗? 确实如此:右值引用既可以是左值也可以是右值,如果有名称则为左值,否则是右值。或者说:作为函数返回值的 && 是右值,直接声明出来的 && 是左值。 这同样也符合前面章节对左值,
        右值的判定方式:其实引用和普通变量是一样的, int &&ref = std::move(a) 和 int a = 5 没有什么区别,等号左边就是左值,右边就是右值。 

最后,从上述分析中我们得到如下结论:
1. 从性能上讲,左右值引用没有区别,传参使用左右值引用都可以避免拷贝。
2. 右值引用可以直接指向右值,也可以通过std::move指向左值;而左值引用只能指向左值(const左值引用也能指向右值)。
3. 作为函数形参时,右值引用更灵活。虽然const左值引用也可以做到左右值都接受,但它无法修改,有一定局限性。 

#include <iostream>using namespace std;void f1(const int& n) {n += 1; // 编译失败,const左值引用不能修改指向变量}void f2(int && n) {n += 1; // ok}int main() {f1(5);f2(5);}

2.4 右值引用使用场景

右值引用优化性能,避免深拷贝

2.4.1浅拷贝重复释放

        对于含有堆内存的类,我们需要提供深拷贝的拷贝构造函数,如果使用默认构造函数,会导致堆内存的重复删除,比如下面的代码:

#include <iostream>
using namespace std;
class A {public:A() :m_ptr(new int(0)) {cout << "constructor A" << endl;}~A() {cout << "destructor A, m_ptr:" << m_ptr << endl;delete m_ptr;m_ptr = nullptr;}private:int* m_ptr;
};
// 为了避免返回值优化,此函数故意这样写
A Get(bool flag) {A a;A b;cout << "ready return" << endl;if (flag) {return a;} else {return b;}
}
int main() {{A a = Get(false); // 运行报错}cout << "main finish" << endl;return 0;
}

2.4.2深拷贝构造函数

        在上面的代码中,默认构造函数是浅拷贝,main函数的 a 和Get函数的 b 会指向同一个指针 m_ptr,在析构的时候会导致重复删除该指针。正确的做法是提供深拷贝的拷贝构造函数,比如下面的代码: 

#include <iostream>
using namespace std;
class A {public:A() :m_ptr(new int(0)) {cout << "constructor A" << endl;}A(const A& a) :m_ptr(new int(*a.m_ptr)) {cout << "copy constructor A" << endl;}~A() {cout << "destructor A, m_ptr:" << m_ptr << endl;delete m_ptr;m_ptr = nullptr;}private:int* m_ptr;
};
// 为了避免返回值优化,此函数故意这样写
A Get(bool flag) {A a;A b;cout << "ready return" << endl;if (flag) {return a;} else {return b;}
}
int main() {{A a = Get(false); // 运行报错}cout << "main finish" << endl;return 0;
}

2.4.3移动构造函数 

        这样就可以保证拷贝构造时的安全性,但有时这种拷贝构造却是不必要的,比如上面代码中的拷贝构造就是不必要的。上面代码中的 Get 函数会返回临时变量,然后通过这个临时变量拷贝构造了一个新的对象 b,临时变量在拷贝构造完成之后就销毁了,如果堆内存很大,那么,这个拷贝构造的代价会很大,带来了额外的性能损耗。有没有办法避免临时对象的拷贝构造呢?答案是肯定的。

 看下面的代码:

#include <iostream>
using namespace std;
class A {public:A() :m_ptr(new int(0)) {cout << "constructor A" << endl;}A(const A& a) :m_ptr(new int(*a.m_ptr)) {cout << "copy constructor A" << endl;}// 移动构造函数,可以浅拷贝A(A&& a) :m_ptr(a.m_ptr) {a.m_ptr = nullptr; // 为防止a析构时delete data,提前置空其m_ptrcout << "move constructor A" << endl;}~A() {cout << "destructor A, m_ptr:" << m_ptr << endl;delete m_ptr;m_ptr = nullptr;}private:int* m_ptr;
};
// 为了避免返回值优化,此函数故意这样写
A Get(bool flag) {A a;A b;cout << "ready return" << endl;if (flag) {return a;} else {return b;}
}
int main() {{A a = Get(false); // 运行报错}cout << "main finish" << endl;return 0;
}

上面的代码中没有了拷贝构造,取而代之的是移动构造( Move Construct)。从移动构造函数的实现中可以看到,它的参数是一个右值引用类型的参数 A&&,这里没有深拷贝,只有浅拷贝,这样就避免了对临时对象的深拷贝,提高了性能。这里的 A&& 用来根据参数是左值还是右值来建立分支,如果是临时值,则会选择移动构造函数。移动构造函数只是将临时对象的资源做了浅拷贝,不需要对其进行深拷贝,从而避免了额外的拷贝,提高性能。这也就是所谓的移动语义( move 语义),右值引用的一个重要目的是用来支持移动语义的。

        移动语义可以将资源(堆、系统对象等)通过浅拷贝方式从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,可以大幅度提高 C++ 应用程序的性能,消除临时对象的维护(创建和销毁)对性能的影响。       

2.5移动(move )语义 

        移动语义可以将资源(堆、系统对象等)通过浅拷贝方式从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,可以大幅度提高 C++ 应用程序的性能,消除临时对象的维护(创建和销毁)对性能的影响。

        move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转义,没有内存拷贝。要move语义起作用,核心在于需要对应类型的构造函数支持。  

 Demo

#include <iostream>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <string.h>
using namespace std;
class MyString {private:char* m_data;size_t m_len;void copy_data(const char *s) {m_data = new char[m_len+1];memcpy(m_data, s, m_len);m_data[m_len] = '\0';}public://构造函数MyString() {m_data = NULL;m_len = 0;}//构造函数MyString(const char* p) {m_len = strlen (p);copy_data(p);}//拷贝构造函数MyString(const MyString& str) {m_len = str.m_len;copy_data(str.m_data);std::cout << "Copy Constructor is called! source: " << str.m_data << std::endl;}//重载=MyString& operator=(const MyString& str) {if (this != &str) {m_len = str.m_len;copy_data(str.m_data);}std::cout << "Copy Assignment  is called! source: " << str.m_data << std::endl;return *this;}//移动构造函数MyString(MyString&& str) {std::cout << "Move Constructor is called! source: " << str.m_data << std::endl;m_len = str.m_len;m_data = str.m_data; //避免了不必要的拷贝str.m_len = 0;str.m_data = NULL;}//重载=MyString& operator=(MyString&& str) {std::cout << "Move Assignment  is called! source: " << str.m_data << std::endl;if (this != &str) {m_len = str.m_len;m_data = str.m_data; //避免了不必要的拷贝str.m_len = 0;str.m_data = NULL;}return *this;}virtual ~MyString() {if (m_data) free(m_data);}
};
int main() {MyString a;a = MyString("Hello");  			// Move Assignment 因为a已经创建了,所以走=,而不是构造函数 MyString b = a; 					// Copy ConstructorMyString c = std::move(a);  		// Move Constructor std::move将左值转为右值std::vector<MyString> vec;vec.push_back(MyString("World"));   // Move Constructorreturn 0;
}

         有了右值引用和转移语义,我们在设计和实现类时,对于需要动态申请大量资源的类,应该设计右值引用的拷贝构造函数和赋值函数,以提高应用程序的效率。

2.6 forward 完美转发 

2.7 emplace_back 减少内存拷贝和移动

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

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

相关文章

51单片机学习:静态数码管实验

实验名称&#xff1a;静态数码管实验 接线说明&#xff1a; 实验现象&#xff1a;下载程序后“数码管模块”最左边数码管显示数字0 注意事项&#xff1a; ***************************…

神经体液调节网络,神经网络能干嘛

神经网络的发展趋势如何&#xff1f; 神经网络的云集成模式还不是很成熟&#xff0c;应该有发展潜力&#xff0c;但神经网络有自己的硬伤&#xff0c;不知道能够达到怎样的效果&#xff0c;所以决策支持系统中并不是很热门&#xff0c;但是神经网络无视过程的优点也是无可替代…

CSDN编程竞赛-第六期(上)

CSDN编程竞赛报名地址&#xff1a;https://edu.csdn.net/contest/detail/16 努力是为了让自己不平庸&#xff1a; 前言/背景 四道题都是相关数组的&#xff0c;思路很好想&#xff0c;但是需要熟练使用&#xff0c;不能有小错误。 参赛流程 活动时间&#xff1a;9月8日-21日&a…

Python机器视觉--OpenCV进阶(核心)--图像直方图与掩膜直方图与直方图均衡化

1.图像直方图 1.1 图像直方图的基本概念 在统计学中&#xff0c;直方图是一种对数据分布情况的图形表示&#xff0c;是一种二维统计图表. 图像直方图是用一表示数字图像中亮度分布的直方图&#xff0c;标绘了图像中每个亮度值的像素数。可以借助观察该直方图了解需要如何调整…

记录一次关于Rank()排序函数问题

先来看应用场景吧 就是页面上有个top按钮 根据不同的top 进行筛选 比如我选择top5 那么在下方当前大区的销售额降序筛选出来最高的前五个销售员or客户这种场景 &#x1f496; 问题 问题1&#xff1a;为什么我的这个rank排序函数 这个华南大区 不是从1开始的呢 其他大区都是正…

java毕业设计选题系统ssm实现的商城系统(电商购物项目)

&#x1f345;文末获取联系&#x1f345; 一、项目介绍 《ssm实现的商城系统》该项目采用技术&#xff1a;springspringMVCmybaitsEasyUIjQueryAjax等相关技术&#xff0c;项目含有源码、文档、配套开发软件、软件安装教程、项目发布教程等 1.1 课题背景、目的及意义 当今社…

java 同学聚会AA制共享账单系统springboot 小程序022

本系统在一般同学会小程序的基础上增加了首页推送最新信息的功能方便用户快速浏览&#xff0c;是一个高效的、动态的、交互友好的同学会小程序。 用户在首页上会看到各类模块的推送内容&#xff0c;可以以最直接的方式获取信息&#xff0c;注册登陆后&#xff0c;可以对应经费信…

Unity基础笔记(5)—— Unity渲染基础与动画系统

Unity渲染基础与动画系统 Unity渲染基础 一、摄像机 1. 摄像机概念和现实中的摄像机很接近,Unity 中 Camera 组件负责将游戏画面拍摄然后投放到画面上 Camera 拍摄到的画面决定了 Game 面板的画面 创建场景的时候,Unity 会默认创建一个摄像机,所以我们点击 Game 面板才有画面…

【算法刷题】链表篇-链表的回文结构

文章目录题目要求方法1&#xff1a;思路代码方法2代码题目要求 链接&#xff1a;链表的回文结构_牛客题霸_牛客网 (nowcoder.com) 1 -> 2 -> 3 -> 2 -> 1 1 -> 2 -> 2 -> 1 上面两个是回文结构 方法1&#xff1a;思路 1.遍历链表&#xff0c;把结点对应的…

网络安全基础——对称加密算法和非对称加密算法(+CA数字证书)

目录 一、数据传输时的安全特性 二、对称加密算法&#xff1a; 三、非对称加密算法 四、对称加密和非对称加密 — 融合算法&#xff1a; 五、CA数字证书&#xff1a; 一、数据传输时的安全特性 ———————————————————————————————————…

分布式进化算法

1 多解优化问题 多解优化问题是指一类具有多个最优解的复杂优化问题。多峰优化问题和多目标优化问题都是两类典型的多解优化问题&#xff0c;它们之前的统一关系&#xff0c;即都具有多个最优解。多峰优化问题要求算法找到多个具有相同适应度值得最优解&#xff0c;多目标优化问…

SpringBoot的核心原理(扒笔记记录)

这一课的主要重点&#xff1a; 自动装配以及starterJDBC数据库连接池ORM、JPA、MyBatis、Hibernate这样相关的一些技术 从Spring到SpringBoot 我们在工作中都可能用过了SpringBoot&#xff0c;特别是最近几点&#xff0c;Java开发者大军里的一员&#xff0c;我们一般可能上手就…

卷积神经网络相比循环神经网络具有哪些特征

CNN卷积神经网络结构有哪些特点&#xff1f; 局部连接&#xff0c;权值共享&#xff0c;池化操作&#xff0c;多层次结构。 1、局部连接使网络可以提取数据的局部特征&#xff1b;2、权值共享大大降低了网络的训练难度&#xff0c;一个Filter只提取一个特征&#xff0c;在整个…

Docker容器互联

前言&#xff1a; 虽然每个docker容器之间都能通过ip来进行互联&#xff0c;但当容器重新启动&#xff0c;ip就会被重新分配给重新启动的容器&#xff0c;这时同个容器由于重启导致ip不一样了&#xff0c;这时就会导致开发和运维的困难程度大大增加&#xff0c;这时候就要考虑…

springboot+学生信息管理 毕业设计-附源码191219

学生信息管理的设计与实现 摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&…

Maven下的依赖管理

依赖管理一. 使用坐标引入jar包二. 快捷方式导入jar包的坐标三. 自动导入设置四. 依赖范围一. 使用坐标引入jar包 使用坐标引入jar包的步骤&#xff1a; 在项目的 pom.xml 中编写 标签在 标签中 使用 引入坐标定义坐标的 groupId&#xff0c;artifactId&#xff0c;version 点…

用于文化遗产的VQA(基于ArtPedia数据集)

艺术 文化遗产领域 VQA parper 阅读 Visual Question Answering for Cultural Heritage 文章目录艺术 文化遗产领域 VQA parper 阅读前言方法visual Question Answering with visual and contextual questionsQuestion Classifier ModuleContextual Question Answering Module…

vue3 | HighCharts实战自定义封装之径向条形图

1.前言 目前正在做vue3的数据可视化项目&#xff0c;vue3的组合式api写法十分方便&#xff0c;可以有各种玩法&#xff0c;有兴趣的同学可以看我个人主页的其他文章。难点是在网上找了一圈的有关径向条形图的示例都没有好的解决方案&#xff0c;决心亲自下手&#xff0c;在其中…

CSP2021初赛游记

csp2022开打,把去年的游记找出来,在这里补了 CSP2021初赛游记 早上7:30去省初门口等crxis,可以和他一起做地铁去,然而最后也就3个学生,准确来说是3个学生加1个家长在等。我当时在微信里和老师说:" 老师你快点过来呀 人好多啊 一大群人在催你 浩浩荡荡 人山人海 局面…

WebKitX ActiveX 5.0.0.15221 Crack

WebKitX ActiveX 封装了 Chromium Embedded Framework (CEF3) 以用于 OLE/COM 语言。Chromium Embedded Framework 封装了 WebKit Blink HTML5 Renderer 和 Google V8 JavaScript Engine。这是一个用于商业用途的生产级稳定组件&#xff0c;将真正在您的桌面和终端应用程序中添…