【C++11】右值引用和移动语义

文章目录

  • 左值和右值的概念
    • 左值
    • 右值
  • 左值与右值引用
    • 移动语义的概念
    • std::move 的作用
    • 使用std::move的注意事项
  • 右值引用的使用场景
  • 右值引用的其他概念
  • 万能引用
  • 完美转发
    • std::forward
    • 万能引用和右值引用的区别
  • 新的类功能
    • 默认成员函数




左值和右值的概念


在C++中, 左值 和 右值 是两种不同的值类型,它们的主要区别在于是否能够引用内存地址。了解这些概念有助于理解变量的作用域、对象的生命周期,以及编译器在表达式求值中的行为。

左值

左值 指的是有持久地址的对象或表达式。换句话说,左值是可以放在赋值运算符左侧的变量或对象,它们可以存储到内存中,并且有明确的内存地址,可以通过引用来操作。

  • 左值通常是变量名或返回变量引用的表达式。
  • 可以对左值取地址(&),因此可以存储和访问它的内存地址。
// 常见的左值
int& func1()
{static int x = 0;return x;
}
普通变量(int x;)
数组元素(arr[0])
对象的成员(obj.member)
出了函数作用域不会销毁的返回值(func1()

右值

指的是在表达式中临时出现的值,它们没有持久的内存地址,也不能通过引用来操作。右值通常是常量、临时对象或表达式的返回值,并且无法对其取地址。

  • 右值通常是常量、临时值、或表达式的计算结果。
  • 右值不允许取地址,因为它们是短暂存在的(例如:临时变量或表达式结果)。
// 常见右值
int func2()
{static int x = 0;return x;
}
字面量(10, 'a', 3.14)
表达式的结果(x + y)
临时对象(std::string("hello"))
出了函数作用域不会销毁的返回值(func2()


左值与右值引用

在C++11中,引入了右值引用的概念,以实现更加高效的内存管理(如移动语义和完美转发)。右值引用使用 && 来表示。

int x = 10;
int& lref = x;         // 左值引用,绑定到左值 x
int&& rref = 10;       // 右值引用,绑定到右值 10

右值引用常用于移动语义,避免不必要的对象拷贝操作,提高程序性能。

在这里插入图片描述

左值引用总结:

  1. 左值引用只能引用左值,不能引用右值。
  2. 但是const左值引用既可引用左值,也可引用右值。
int main()
{// 左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a;   // ra为a的别名//int& ra2 = 10;   // 编译失败,因为10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;
}

右值引用总结:

  1. 右值引用只能右值,不能引用左值。
  2. 但是右值引用可以move以后的左值。
int main()
{// 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 无法从“int”转换为“int &&”// message : 无法将左值绑定到右值引用int a = 10;int&& r2 = a;// 右值引用可以引用move以后的左值int&& r3 = std::move(a);return 0;
}

移动语义的概念

在C++中,构造和赋值操作通常有两种形式:

  • 拷贝语义:通过拷贝构造函数或拷贝赋值运算符将对象的资源逐一复制。拷贝操作通常比较昂贵。
  • 移动语义:通过移动构造函数或移动赋值运算符将对象的资源“转移”到另一个对象,而不是逐一复制资源,通常只涉及指针或资源句柄的转移。移动操作通常是轻量级的,因为它不需要真正的资源复制。

移动语义使用右值引用(T&&)来实现,这使得可以安全地“窃取”另一个对象的资源。右值引用表示对临时对象(右值)的引用,可以安全地对其进行“移动”操作。

std::move 的作用

std::move的作用是将一个对象显式地转换为右值引用,以便可以调用移动构造函数或移动赋值运算符。std::move本质上并不会移动对象本身,而是告诉编译器可以安全地将其资源“移动”到目标对象中。

#include <iostream>
#include <vector>class MyClass {
public:MyClass(int size) : data(new int[size]), size(size) {std::cout << "Constructed\n";}// 移动构造函数MyClass(MyClass&& other) noexcept : data(other.data), size(other.size) {other.data = nullptr;  // 确保原对象不会再管理这段资源other.size = 0;std::cout << "Move Constructed\n";}// 析构函数~MyClass() {delete[] data;std::cout << "Destroyed\n";}private:int* data;int size;
};int main() {MyClass a(10);MyClass b = std::move(a);  // 使用std::move调用移动构造函数return 0;
}

在这个例子中,std::move(a)将a显式地转换为右值引用,从而调用了移动构造函数,而不是拷贝构造函数。

使用std::move的注意事项

  1. 确保不再使用被移动对象的资源:移动之后,被移动对象处于一种有效但未指定的状态。通常,它的资源已经被转移,因此不要再使用其资源,除非对其进行重新赋值。

  2. 移动和异常安全:实现移动构造函数和移动赋值运算符时,通常要加上noexcept,以确保在容器(如std::vector)发生重新分配时能够使用移动操作而不是拷贝操作。容器在移动时,如果移动操作不保证不会抛出异常,它们可能会退回到使用更昂贵的拷贝操作。

  3. 不能对左值使用std::move:如果你错误地对一个将来还会使用的左值对象调用std::move,会导致程序逻辑错误或运行时错误。被移动对象的资源被转移后,它就不再具有原来的功能了。

  4. 与右值引用配合使用std::move通常用于函数返回值或传递到函数参数时,特别是在需要避免拷贝而是移动资源的场合。通过右值引用参数来避免不必要的拷贝,从而提高效率。


右值引用的使用场景


在说右值引用之前,我先给大家看一下关于编译器的一些优化:需要用到之前模拟实现的string类

在这里插入图片描述
这里用了一个常量赋值给一个正在初始化的对象。编译器就直接优化成了一次普通构造。

那如果写成这样呢:


这里我画一个图帮大家理解一下:
在这里插入图片描述
所以平时如果我们需要定义一个对象,最好的方式就是在定义时候初始化了,不然后面再进行初始化代价是比较大的。

这里的优化是编译器替我们做了的。那么我再通过右值引用来进行一下优化。

在这里插入图片描述
此时,我们就算先定义对象,再初始化对象,花费的代价也比没有加右值引用版的拷贝赋值代价低了很多。

移动赋值的代价是极低的,因为这里是直接将一个将亡值(右值)的资源与我们需要赋值的对象的资源交换一下,并让将亡值带着赋值对象原本的资源删除。


右值引用的其他概念


右值引用还有一个概念:一个右值被右值引用了以后,该右值引用是具有左值属性。
在这里插入图片描述

设计这个特性的原因是:移动语义是将右值的资源窃取过来,如果右值引用没有左值属性的话,是无法交换的,因为是左值,所以它的资源才能被改变(交换)。


万能引用


在C++中,“万能引用”(是一个用来同时匹配左值引用和右值引用的引用类型。它的定义依赖于类型推导,是C++11引入的特性之一。

定义万能引用的条件:

  1. 万能引用通常使用T&&的形式,但它必须在类型推导的上下文中。
  2. 如果类型是通过模板参数推导出来的,并且形式为T&&,那么它就是万能引用。
template <typename T>
void func(T&& param) {// param 是万能引用
}

在这个例子中,T&&是万能引用,因为T的类型是由传入的实参推导出来的。如果传入的是左值,T&&会被推导为左值引用(T&),如果传入的是右值,T&&会被推导为右值引用(T&&)。

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
// 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
// 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
template<typename T>
void PerfectForward(T&& t)
{Fun(t);
}int main()
{PerfectForward(10);             // 右值int a;PerfectForward(a);             // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b);            // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}

在这里插入图片描述


完美转发


在C++中,完美转发是一种用于将函数参数保持其原有特性(左值或右值)传递给其他函数的技术。这在编写泛型代码时非常有用,因为它允许你转发参数而不会丢失其特性,提高了代码的灵活性和效率。

完美转发通常用于模板函数中,尤其是在实现泛型包装器或中间层函数时,可以将接收到的参数“完美”地转发给目标函数。

std::forward

std::forward 是 C++ 标准库中的一个模板函数,用于在特定条件下将参数转发,以保留其左值或右值特性。它通常与万能引用配合使用,以实现完美转发。

std::forward 的主要应用场景是在实现泛型函数时,尤其是当一个函数希望将其收到的参数转发给另一个函数,而不改变参数的左值或右值特性时。

以上面那个例子为例:
在这里插入图片描述

万能引用和右值引用的区别

右值引用,用于绑定右值的引用类型,允许对右值进行修改和资源的移动操作。右值引用的典型用途是实现移动语义,以提高程序效率。

右值引用的特点

  • 右值引用的声明是 T&&,但只用于绑定右值。
  • 右值引用通常用于捕获即将被销毁的对象,从而进行资源的“移动”操作而不是复制。
  • 右值引用可以用来实现移动构造函数和移动赋值运算符,提高对象的移动效率。
class MyClass {
public:MyClass(int&& x) {// 右值引用构造函数}MyClass(MyClass&& other) {// 移动构造函数}
};

万能引用可以绑定左值和右值。这主要出现在模板类型推导的场景中。万能引用能够让函数对参数进行完美转发,因此广泛应用于泛型编程。

万能引用的特点:

  • 万能引用同样使用 T&& 的形式,但它依赖于类型推导。
  • 如果 T&& 出现在一个模板函数中,且 T 是由调用时推导出来的(而不是显式指定的),那么这个 T&& 就是万能引用。
  • 万能引用可以同时绑定到左值和右值,这使得它在编写泛型函数时特别有用,可以根据传入参数的特性来决定如何处理引用。

如何判断是万能引用?

  • 万能引用只出现在模板函数或者具有自动类型推导的函数中。
  • 如果 T&& 的类型 T 是由调用时推导而来的,那么 T&& 就是万能引用。
template <typename T>
void func(T&& param) {// 这里的 T&& 是万能引用
}


新的类功能


默认成员函数

原来C++类中,有6个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。C++11 新增了两个:移动构造函数和移动赋值运算符重载。

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

  • 如果你没有自己实现移动构造函数,且没有实现析构函数拷贝构造拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数拷贝构造拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)

  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

强制生成默认函数的关键字default:
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p):_name(p._name), _age(p._age){}Person(Person && p) = default;
private:hyt::string _name;int _age;
};
int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}

禁止生成默认函数的关键字delete:
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁而已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上 = delete即可,该语法指示编译器不生成对应函数的默认版本,称 = delete修饰的函数为删除函数。

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p) = delete;
private:hyt::string _name;int _age;
};
int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}

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

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

相关文章

MATLAB下的RSSI定位程序,二维平面上的定位,基站数量可自适应

文章目录 引言程序概述程序代码运行结果待定位点、锚点、计算结果显示待定位点和计算结果坐标 引言 随着无线通信技术的发展&#xff0c;基于 R S S I RSSI RSSI&#xff08;接收信号强度指示&#xff09;的方法在定位系统中变得越来越流行。 R S S I RSSI RSSI定位技术特别适…

面试题之- null和undefined的区别

前言 首先undefined和null都是基本数据类型&#xff0c;这两个基本数据类型分别都只有一个值&#xff0c;就是undefined和null。 undefined代表的含义是未定义&#xff0c;null代表的的含义是空对象&#xff0c;一般变量声明了但是还有没有定义的时候会返回undefined&#xf…

毕设 大数据抖音短视频数据分析与可视化(源码)

文章目录 0 前言1 课题背景2 数据清洗3 数据可视化地区-用户观看时间分界线每周观看观看路径发布地点视频时长整体点赞、完播 4 进阶分析相关性分析留存率 5 深度分析客户价值判断 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕…

Python小示例——质地不均匀的硬币概率统计

在概率论和统计学中&#xff0c;随机事件的行为可以通过大量实验来研究。在日常生活中&#xff0c;我们经常用硬币进行抽样&#xff0c;比如抛硬币来决定某个结果。然而&#xff0c;当我们处理的是“质地不均匀”的硬币时&#xff0c;事情就变得复杂了。质地不均匀的硬币意味着…

Oracle 表空间异构传输

已经有了表空间的数据文件&#xff0c;和元数据dump文件&#xff0c;如何把这个表空间传输到异构表空间中&#xff1f; 查询异构传输平台信息&#xff1a; COLUMN PLATFORM_NAME FORMAT A40 SELECT PLATFORM_ID, PLATFORM_NAME, ENDIAN_FORMAT FROM V$TRANSPORTABLE_PLATFORM O…

LLM 构建Data Multi-Agents 赋能数据分析平台的实践之⑥:NL2SQL技术探讨

一、概述 NL2SQL&#xff08;Natural Language to SQL&#xff09;是一种将自然语言转换为结构化查询语言的技术。它可以帮助用户通过使用自然语言来与数据库进行交互&#xff0c;而无需了解复杂的SQL语法。 NL2SQL技术的背景&#xff1a; 随着人工智能的发展&#xff0c;越…

【Python】AudioFlux:音频与音乐分析的利器

AudioFlux 是一个专为音频和音乐分析、特征提取设计的开源 Python 库。它支持广泛的音频处理功能&#xff0c;包括特征提取、音高检测、时频分析、谱图处理等。这些功能被广泛应用于机器学习、深度学习、信号处理等领域&#xff0c;特别是对于音乐信息检索&#xff08;MIR&…

Unity WebGL使用nginx作反向代理处理跨域,一些跨域的错误处理(添加了反向代理的配置依旧不能跨域)

反向代理与跨域描述 什么是跨域&#xff1f; 跨域&#xff08;Cross-Origin Resource Sharing, CORS&#xff09;是指在浏览器中&#xff0c;当一个网页的脚本试图从一个域名&#xff08;协议、域名、端口&#xff09;请求另一个域名的资源时&#xff0c;浏览器会阻止这种请求…

《精通开关电源设计》笔记一

重点 效率 纹波 环路响应 尺寸&#xff0c;从静态到动态的研究方法&#xff0c;假设开关电源稳态运行&#xff0c;以电感为中心&#xff0c;根据半导体器件(mos管或二极管)分段分析电路的状态&#xff0c;工具有电路原理和能量守恒 影响效率的主要是开关损耗&#xff0c;所以…

qemu模拟arm64环境-构建6.1内核以及debian12

一、背景 手头没有合适的arm64开发板&#xff0c;但是需要arm的环境&#xff0c;于是想到qemu模拟一个。除了硬件交互以外&#xff0c;软件层面的开发还是都可以实现的。 虚拟机还能自定义内存大小和镜像大小&#xff0c;非常适合上板前的验证&#xff0c;合适的话再买也不迟。…

OpenGL笔记之事件驱动设计将相机控制类和应用程序类分离

OpenGL笔记之事件驱动设计将相机控制类和应用程序类分离 —— 2024-10-02 下午 bilibili赵新政老师的教程看后笔记 code review! 文章目录 OpenGL笔记之事件驱动设计将相机控制类和应用程序类分离1.代码图片2.分析3.UML4.代码 1.代码图片 运行 Mouse button 1 pressed at (1…

掌控物体运动艺术:图扑 Easing 函数实践应用

现如今&#xff0c;前端开发除了构建功能性的网站和应用程序外&#xff0c;还需要创建具有吸引力且尤为流畅交互的用户界面&#xff0c;其中动画技术在其中发挥着至关重要的作用。在数字孪生领域&#xff0c;动画的应用显得尤为重要。数字孪生技术通过精确模拟现实世界中的对象…

笔记整理—linux进程部分(6)进程间通信、alarm和pause

两个进程间通信可能是任何两个进程间的通信&#xff08;IPC&#xff09;。同一个进程是在同一块地址空间中的&#xff0c;在不同的函数与文件以变量进程传递&#xff0c;也可通过形参传递。2个不同进程处于不同的地址空间&#xff0c;要互相通信有难度&#xff08;内存隔离的原…

华为海思:大小海思的双轮驱动战略分析

华为海思,作为华为旗下的半导体设计部门,近年来在芯片设计领域取得了显著成就,成为了中国乃至全球芯片设计的重要力量。实际上,华为海思并非单一实体,而是由两个主要分支构成:大海思和小海思。这两个分支虽然同属华为海思,但在定位、产品布局以及市场策略上有所不同,共…

【AI学习】Mamba学习(三):离散化SSM的矩阵计算

SSM离散化表示 除了连续的输入之外&#xff0c;还会通常碰到离散的输入(如文本序列)。所以SSM需要离散化形式&#xff0c;就是下面公式2和3。 SSM离散化过程 但是好奇这个离散化过程是如何进行的&#xff1f; 《一文通透想颠覆Transformer的Mamba&#xff1a;从SSM、HiPPO、…

JDBC 概述

JDBC 概述 JDBC的基本概念与功能JDBC的工作原理JDBC的组件与类JDBC的类型与特性JDBC的应用场景 JDBC&#xff08;Java Database Connectivity&#xff09;即Java数据库连接&#xff0c;是Java编程语言用于与数据库进行连接和操作的API&#xff08;应用程序编程接口&#xff09;…

9个微服务最佳实践

1⃣分离数据存储&#xff1a;独立数据库&#xff0c;提升灵活性。 2⃣代码成熟度一致&#xff1a;质量稳定&#xff0c;避免技术债务 3⃣独立构建流程&#xff1a;独自构建&#xff0c;快速部署。 4⃣单一职责原则&#xff1a;业务功能单一&#xff0c;简化维护。 5⃣容器化部署…

TS1 order set分析

如下图&#xff0c;所示为TS1 order序列。该序列有16个symbol组成。 常见的symbol有&#xff0c;PAD和COM等。PAD是K symbol&#xff0c;还有D symbol。下文先给出COM symbol的解读。读协议文档可知COM常被称为K28.5。K是symbol的类型&#xff0c;注意symbol是编码过的数据。K…

DatePicker 日期控件

效果&#xff1a; 要求&#xff1a;初始显示系统当前时间&#xff0c;点击日期控件后修改文本控件时间。 目录结构&#xff1a; activity_main.xml(布局文件)代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:and…

免费版U盘数据恢复软件大揭秘,拯救你的重要数据

我们的生活和工作越来越离不开各种存储设备&#xff0c;其中优盘因其小巧便携、方便使用的特点&#xff0c;成为了我们存储和传输数据的重要工具之一。为了防止你像我一样会遇到数据丢失抓狂的情况&#xff0c;我分享几款u盘数据恢复软件免费版工具来即时补救。 1.福昕U盘数据…