42.C++11-右值引用与移动语义/完美转发

news2025/5/19 11:52:46

⭐上篇文章:41.C++哈希6(哈希切割/分片/位图/布隆过滤器与海量数据处理场景)-CSDN博客

⭐本篇代码:c++学习/22.C++11新特性的使用 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)

⭐标⭐是比较重要的部分

目录

一. 右值引用的概念

1.1 左值引用与右值引用

 1.2  右值引用引用左值,左值引用引用右值

​编辑 1.3 右值与左值在函数参数中的匹配⭐

二. 纯右值与将亡值 

三. 移动语义 ⭐

 3.1 右值引用做函数参数 - 移动构造

3.2 右值引用做函数参数 - 移动赋值

 3.3 右值引用与返回值问题

四. 完美转发 ⭐

4.1 万能引用

4.2 std::forward实现完美转发

 五. 引用总结


一. 右值引用的概念

1.1 左值引用与右值引用

        引用是给对象取别名,左值引用是给左值取别名,右值引用是给右值取别名。

左值一般为用户定义的可修改的变量,而右值一般是常量,返回值,表达式等临时变量。

一般认为:左值是能够取地址的,而右值无法取地址。

        一般使用&&符号去引用右值。如下面代码中的常量10,表达式x+y 

#include <iostream>
using namespace std;

int main()
{
    // 1.使用 &去引用左值
    int a = 10, b = 5;
    int &a1 = a, &b1 = b;
    cout << a1 << " " << b1 << endl;

    // 2.使用&&去引用右值
    int x = 10, y = 20;
    int &&x1 = 10;
    int &&x2 = x + y;
    cout << x1 << " " << x2 << endl;

    x1 = 12;
    x2 = 10;
    cout << x1 << " " << x2 << endl;
    return 0;
}

 1.2  右值引用引用左值,左值引用引用右值

        一般来说,这两种操作都是无法直接实现的。

 1.3 右值与左值在函数参数中的匹配⭐

        假如有三个重载函数,一个参数为左值引用,一个为const 左值引用,一个为右值引用(没有const 右值引用,因为右值引用过程有资源转移)。

        使用这些函数去调用左值/const左值与右值,最终会匹配哪一个函数呢?测试代码如下:

#include <iostream>
using namespace std;

template <class T>
void f1(T &a)
{
    cout << "void f1(T &a)" << endl;
}

template <class T>
void f1(const T &a)
{
    cout << "void f1(const T &a)" << endl;
}

template <class T>
void f1(T &&a)
{
    cout << "void f1(T &&a)" << endl;
}

int main()
{
    int a = 1, b = 2;
    const int c = 10;
    f1(a);
    f1(c);
    f1(a + b);
    f1(10);

    return 0;
}

首先根据代码推测一下运行的结果:第一条应该是T&a,第二条是const T&a,第三条/四条是T&&a

运行结果如下:

可以看到,推测的结果是正确的!

如果我将右值引用的重载函数注释掉呢?理论来说,此时调用右值应该匹配的是const版本。

运行结果如下:

这表示:编译器可以识别右值与左值。

二. 纯右值与将亡值 

        纯右值是指纯粹的右值:比如常量( 10,"hello" ),非引用类型的返回值( int func() ),表达式(a +b),临时对象。这些值一般无标识,无法取地址。

        而将亡值是即将销毁的左值,比如move(左值),右值引用的函数调用。将亡值常用来实现移动语义。

三. 移动语义 ⭐

        移动语义指的是,将一个对象的资源直接转移到另一个对象中,减少深拷贝的消耗。通过移动语义来实现右值引用作为参数和右值引用返回,从而达到减少数据的拷贝。

        移动语义移动的值是不再使用的值,如传值返回,传入临时对象/右值对象做函数参数。

        使用移动语义的时候,一般将函数标记为noexcept,即不会抛出异常。这样编译器就会减少异常处理的代码,并且执行更为激进的操作,比如直接转移资源以提高性能

 3.1 右值引用做函数参数 - 移动构造

        测试代码如下:分别使用左值引用拷贝构造和右值引用实现移动构造。

#include <iostream>
#include <cstring>
using namespace std;

class String
{
public:
    String(const char *str = "")
    {
        if (nullptr == str)
            str = "";
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }

    // 右值引用,但是是深拷贝
    String(const String &s)
        : _str(new char[strlen(s._str) + 1])
    {
        cout << " String(const String &s) 深拷贝" << endl;
        strcpy(_str, s._str);
    }

    String(String &&s) noexcept
        : _str(s._str)
    {
        cout << "   String(String &&s) noexcept 移动拷贝" << endl;
        s._str = nullptr;
    }

    ~String()
    {
        if (_str)
            delete[] _str;
    }

private:
    char *_str;
};

String f(const char *str)
{
    return String(str);
}

int main()
{
    String s("123");
    cout << " ------------------------------" << endl;
    String s1(s);
    cout << " ------------------------------" << endl;

    // 由于编译器优化,直接转移资源,不调用移动构造
    cout << " ------------------------------" << endl;
    String s2(String("临时对象-右值"));
    cout << " ------------------------------" << endl;

    cout << " ------------------------------" << endl;
    String s3(f("临时返回值-右值"));
    cout << " ------------------------------" << endl;

    // 通过move强制使用移动构造
    cout << " ------------------------------" << endl;
    String s4(move(s1));
    cout << " ------------------------------" << endl;
    return 0;
}

测试结果如下:

这样一来,我们就能够避免传入右值对象的时候使用深拷贝这种开销大的操作了。

假如我们将移动构造注释掉,就会发生使用右值对象传参的时候调用深拷贝。

测试如下:

3.2 右值引用做函数参数 - 移动赋值

        类中除了拷贝函数,还有重载赋值运算符。此时如果不使用右值引用做参数,并实现移动拷贝。传入右值就会调用开销大的深拷贝!

#include <iostream>
#include <cstring>
using namespace std;

class String
{
public:
    String(const char *str = "")
    {
        if (nullptr == str)
            str = "";
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }

    // 右值引用,但是是深拷贝
    String(const String &s)
        : _str(new char[strlen(s._str) + 1])
    {
        cout << " String(const String &s) 深拷贝" << endl;
        strcpy(_str, s._str);
    }

    String(String &&s) noexcept
        : _str(s._str)
    {
        cout << "   String(String &&s) noexcept 移动拷贝" << endl;
        s._str = nullptr;
    }

    String &operator=(const String &s)
    {
        if (this != &s)
        {
            cout << "左值-String& operator=(const String& s)-深拷贝" << endl;
            delete[] _str; // 防止内存泄漏
            _str = new char[strlen(s._str) + 1];
            strcpy(_str, s._str);
        }
        return *this;
    }

    String &operator=(String &&s) noexcept // 不能使用const,否则无法swap
    {

        cout << "右值-- String& operator=(String&& s) -- 移动拷贝赋值效率高)" << endl;
        swap(_str, s._str);

        return *this;
    }

    ~String()
    {
        if (_str)
            delete[] _str;
    }

private:
    char *_str;
};

String f(const char *str)
{
    return String(str);
}

int main()
{
    String s("123");


    String s5;
    s5 = s;
    s5 = String("临时对象-右值");
    s5 = f("临时返回值-右值");

    return 0;
}

运行结果如下:

        可以看到,当传入的值是右值时候,会调用移动赋值来减少深拷贝,从而提高效率。

同理:如果注释了移动赋值,传入右值就会调用深拷贝!

 3.3 右值引用与返回值问题

        正常的传值返回,首先需要深拷贝构造一个临时对象,然后在根据临时对象深拷贝获取我们需要的结果。

        而有了右值引用,传值返回的时候就只需要一次深拷贝和一次移动赋值即可。1 深拷贝构造一个临时对象。2 调用移动赋值/拷贝将数据转移给返回的对象。

四. 完美转发 ⭐

        完美转发是:在函数模板中,完全依照依照模板的参数类型,将参数传递给函数模板中调用的另一个函数。即保留参数的原始属性和类型传递。

        为何需要完美转发:因为在函数传参的过程中,有可能将右值看成左值,这样就不会触发移动语义,造成性能的损失。

4.1 万能引用

        当使用 T&&作为模板参数的时候,可以匹配任意类型的左值和右值。

如下面的代码:

#include <iostream>
using namespace std;

void Fun(int &x) { cout << "lvalue ref" << endl; }
void Fun(int &&x) { cout << "rvalue ref" << endl; }
void Fun(const int &x) { cout << "const lvalue ref" << endl; }
void Fun(const int &&x) { cout << "const rvalue ref" << endl; }

template <typename T>
void PerfectForward(T &&t)
{
    Fun(t);       // 都调用了左值,右值引用会在第二次传参的时候,其属性丢失
    Fun(move(t)); // 都调用了右值,右值引用会在第二次传参的时候,其属性丢失
}

int main()
{
    int a;
    PerfectForward(a);       // lvalue ref
    PerfectForward(move(a)); // rvalue ref
    const int b = 8;
    PerfectForward(b);       // const lvalue ref
    PerfectForward(move(b)); // const rvalue ref
    return 0;
}

        虽然我们传入的值有右值,有左值。但是直接调用Func会导致传入的类型改变。Func直接传入模板的参数,只会调用左值引用的函数。而move参数只会调用右值引用的函数。

        运行结果如下:

4.2 std::forward实现完美转发

        使用std::forward即可实现完美转发。

        通过完美转发,可以帮助完美正确的调用右值,从而实现移动语义,减少拷贝语义带来的性能上的损失。提高代码的效率。

 五. 引用总结

1 无论是左值引用还是右值引用,本质都是为了减少不必要的拷贝来提高程序的运行效率。

2 右值引用是左值引用的补充,通过移动构造/赋值减少临时对象的拷贝次数。

左值引用:

1 做参数:解决参数需要拷贝的问题

2 做返回值:用于直接返回在堆上创建的数据,减少拷贝

右值引用:

1 做参数:解决使用临时对象还需要拷贝的问题,直接将临时对象转移到我创建的对象上

2 返回值优化:减少接收返回值的时候需要两次深拷贝的问题,第二次直接将临时对象的资源转移到我接收的对象中。

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

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

相关文章

LeetCode题二:判断回文

查阅资料我得到的结果远没有大佬们的做法更省时间&#xff0c;而且还很麻烦 我的代码(完整)&#xff1a; class Solution:def isPalindrome(self, x: int) -> bool:# 若 x 为负数&#xff0c;由于负数不可能是回文数&#xff0c;直接返回 Falseif x < 0:return False# …

[王阳明代数讲义]琴语言类型系统工程特性

琴语言类型系统工程特性 层展物理学组织实务与艺术与琴生生.物机.械科.技工.业研究.所软凝聚态物理开发工具包社会科学气质砥砺学人生意气场社群成员魅力场与心气微积分社会关系力学 意气实体过程图论信息编码&#xff0c;如来码导引 注意力机制道装Transformer架构的发展标度律…

问题:tomcat下部署eureka双重路径

开发时在tomcat下启动eureka服务 客户端注册时需要地址需要注意 http://localhost:8761/eureka/eureka 后面一个eureka与tomcat context-path有关系按实际配置替换 如果不想要两个path可将tomcat context-path写为 / 建议使用 / 避免出现其他问题 如图

React(九)React Hooks

初识Hook 我们到底为什么需要hook那? 函数组件类组件存在问题 函数组件存在的问题&#xff1a; import React, { PureComponent } from reactfunction HelloWorld2(props) {let message"Hello world"// 函数式组件存在的缺陷&#xff1a;// 1.修改message之后&a…

《AI大模型应知应会100篇》加餐篇:LlamaIndex 与 LangChain 的无缝集成

加餐篇&#xff1a;LlamaIndex 与 LangChain 的无缝集成 问题背景&#xff1a;在实际应用中&#xff0c;开发者常常需要结合多个框架的优势。例如&#xff0c;使用 LangChain 管理复杂的业务逻辑链&#xff0c;同时利用 LlamaIndex 的高效索引和检索能力构建知识库。本文在基于…

元素三大等待

硬性等待&#xff08;强制等待&#xff09; 线程休眠&#xff0c;强制等待 Thread.sleep(long millis);这是最简单的等待方式&#xff0c;使用time.sleep()方法来实现。在代码中强制等待一定的时间&#xff0c;不论元素是否已经加载完成&#xff0c;都会等待指定的时间后才继…

【DY】信息化集成化信号采集与处理系统;生物信号采集处理系统一体机

MD3000-C信息化一体机生物信号采集处理系统 实验平台技术指标 01、整机外形尺寸&#xff1a;1680mm(L)*750mm(w)*2260mm(H)&#xff1b; 02、实验台操作面积&#xff1a;750(w)*1340(L&#xff09;&#xff08;长*宽&#xff09;&#xff1b; 03、实验台面离地高度&#xf…

康谋分享 | 仿真驱动、数据自造:巧用合成数据重构智能座舱

随着汽车向智能化、场景化加速演进&#xff0c;智能座舱已成为人车交互的核心承载。从驾驶员注意力监测到儿童遗留检测&#xff0c;从乘员识别到安全带状态判断&#xff0c;座舱内的每一次行为都蕴含着巨大的安全与体验价值。 然而&#xff0c;这些感知系统要在多样驾驶行为、…

Vue 数据传递流程图指南

今天&#xff0c;我们探讨一下 Vue 中的组件传值问题。这不仅是我们在日常开发中经常遇到的核心问题&#xff0c;也是面试过程中经常被问到的重要知识点。无论你是初学者还是有一定经验的开发者&#xff0c;掌握这些传值方式都将帮助你更高效地构建和维护 Vue 应用 目录 1. 父…

【C语言】strstr查找字符串函数

一、函数介绍 strstr 是 C 语言标准库 <string.h> 中的字符串查找函数&#xff0c;用于在主字符串中查找子字符串的首次出现位置。若找到子串&#xff0c;返回其首次出现的地址&#xff1b;否则返回 NULL。它是处理字符串匹配问题的核心工具之一。 二、函数原型 char …

机器学习、深度学习和神经网络

机器学习、深度学习和神经网络 术语及相关概念 在深入了解人工智能&#xff08;AI&#xff09;的工作原理以及它的各种应用之前&#xff0c;让我们先区分一下与AI密切相关的一些术语和概念&#xff1a;人工智能、机器学习、深度学习和神经网络。这些术语有时会被交替使用&#…

数字孪生在智慧城市中的前端呈现与 UI 设计思路

一、数字孪生技术在智慧城市中的应用与前端呈现 数字孪生技术通过创建城市的虚拟副本&#xff0c;实现了对城市运行状态的实时监控、分析与预测。在智慧城市中&#xff0c;数字孪生技术的应用包括交通流量监测、环境质量分析、基础设施管理等。其前端呈现主要依赖于Web3D技术、…

Android OpenGLES 360全景图片渲染(球体内部)

概述 360度全景图是一种虚拟现实技术&#xff0c;它通过对现实场景进行多角度拍摄后&#xff0c;利用计算机软件将这些照片拼接成一个完整的全景图像。这种技术能够让观看者在虚拟环境中以交互的方式查看整个周围环境&#xff0c;就好像他们真的站在那个位置一样。在Android设备…

LETTERS(DFS)

【题目描述】 给出一个rowcolrowcol的大写字母矩阵&#xff0c;一开始的位置为左上角&#xff0c;你可以向上下左右四个方向移动&#xff0c;并且不能移向曾经经过的字母。问最多可以经过几个字母。 【输入】 第一行&#xff0c;输入字母矩阵行数RR和列数SS&#xff0c;1≤R,S≤…

NVM 多版本Node.js 管理全指南(Windows系统)

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、全栈领域优质创作者、高级开发工程师、高级信息系统项目管理师、系统架构师&#xff0c;数学与应用数学专业&#xff0c;10年以上多种混合语言开发经验&#xff0c;从事DICOM医学影像开发领域多年&#xff0c;熟悉DICOM协议及…

C,C++语言缓冲区溢出的产生和预防

缓冲区溢出的定义 缓冲区是内存中用于存储数据的一块连续区域&#xff0c;在 C 和 C 里&#xff0c;常使用数组、指针等方式来操作缓冲区。而缓冲区溢出指的是当程序向缓冲区写入的数据量超出了该缓冲区本身能够容纳的最大数据量时&#xff0c;额外的数据就会覆盖相邻的内存区…

《Linux内存管理:实验驱动的深度探索》【附录】【实验环境搭建 2】【vscode搭建调试内核环境】

1. 如何调试我们的内核 1. GDB调试 安装gdb sudo apt-get install gdb-multiarchgdb-multiarch是多架构版本&#xff0c;可以通过set architecture aarch64指定架构 QEMU参数修改添加-s -S #!/usr/bin/shqemu-7.2.0-rc1/build/aarch64-softmmu/qemu-system-aarch64 \-nogr…

Flutter项目之登录注册功能实现

目录&#xff1a; 1、页面效果2、登录两种状态界面3、中间按钮部分4、广告区域5、最新资讯6、登录注册页联调6.1、网络请求工具类6.2、注册页联调6.3、登录问题分析6.4、本地缓存6.5、共享token6.6、登录页联调6.7、退出登录 1、页面效果 import package:flutter/material.dart…

ctfshow VIP题目限免 源码泄露

根据题目提示是源代码泄露&#xff0c;右键查看页面源代码发现了 flag

移动神器RAX3000M路由器变身家庭云之七:增加打印服务,电脑手机无线打印

系列文章目录&#xff1a; 移动神器RAX3000M路由器变身家庭云之一&#xff1a;开通SSH&#xff0c;安装新软件包 移动神器RAX3000M路由器变身家庭云之二&#xff1a;安装vsftpd 移动神器RAX3000M路由器变身家庭云之三&#xff1a;外网访问家庭云 移动神器RAX3000M路由器不刷固…