27. 右值引用

一、什么是右值引用

  右值引用是 C++11 中引入的一种重要特性,它主要用于支持移动语义和完美转发。右值引用 是对右值(即临时对象或即将被销毁的对象)的引用,允许我们直接操作这些对象的资源,而无需进行拷贝。在 C++98 中,临时对象(右值)在赋值给函数参数时,只能被接受为 const 引用,这意味着函数无法修改这些对象的值。C++11 引入右值引用后,我们可以使用非常量引用(即右值引用)来接收这些临时对象,并在函数中直接操作它们。

  左值和右值是表达式的分类,它们的主要区别在于表达式的值是否可以取地址。左值是可以取地址的表达式,而右值通常是不能取地址的表达式。

  • 左值(Lvalue):通常有明确存储地址的表达式,如变量、对象的名称等。
  • 右值(Rvalue):通常是没有明确存储地址的表达式,如字面量、临时对象、返回临时对象的表达式等。

  右值引用是对右值的引用,它使用 && 操作符来声明。右值引用的主要目的是为了实现移动语义,即允许资源的所有权从一个对象转移到另一个对象,从而避免不必要的拷贝,提高性能。

#include <iostream>using namespace std;int main(void)
{int num= 10;            // 左值int &a = num;           // 左值引用int && b = 30;          // 右值引用const int &c = num;     // 常量左值引用const int &&d = 50;     // 常量右值引用// 左值引用可以使用右值引用初始化int &e = b;const int &f = b;const int &g = d; // 右值引用只能使用右值初始化return 0;
}

二、右值引用的作用

  右值引用就是对一个右值进行引用的类型。因为右值是匿名的,所以我们只能通过引用方式找到它。无论声明左值引用还是右值引用都必须立即进行初始化,这是因为引用本身并不拥有所绑定对象的内存,它只是该对象的一个别名。通过右值引用的声明,该右值又 “重获新生”,其 生命周期与右值引用类型变量的生命周期一样,只要该变量还存活,该右值临时变量将会一直存活下去。

#include <iostream>using namespace std;class MyClass 
{
private:int *p;public:MyClass(void);                          // 无参构造函数MyClass(const MyClass &other);          // 拷贝构造函数~MyClass(void);                         // 析构函数
};MyClass::MyClass(void) : p(new int(100))
{cout << "Constructor called" << endl;
}MyClass::MyClass(const MyClass &other) : p(new int(*other.p))
{cout << "Copy constructor called" << endl;
}MyClass::~MyClass(void) 
{cout << "Destructor called" << endl;delete p;
}MyClass getMyClass(void) 
{MyClass temp;return temp;                            // 返回一个临时对象,这是一个右值
}int main(void) 
{MyClass obj = getMyClass();return 0;
}

  这里,我们要使用命令 gcc -o template.cpp -fbi-elide-constructors 来手动编译 C++ 程序,使用命令行参数 -fno-elide-constructors 用于关闭函数返回值优化(RVO)。这是因为 GCC 的 RVO 优化会减少复制构造函数的调用。

  运行这段程序,会发现该程序发生三次构造。首先 getMyClass() 函数中 MyClass temp;会调用 无参的构造函数,然后 return temp;会使用 复制构造 产生临时对象,接着 MyClass obj = GetMyClass(); 会使用 复制构造 将临时对象复制到 obj,最后临时对象被销毁。

  但如果将 MyClass obj = getMyClass();替换为 MyClass &&obj = getMyClass(); 使用右值引用后,会调用两次构造函数,一次是 getMyClass() 中 MyClass temp 会调用 无参的构造函数,另一次是 return temp; 会使用 复制构造 产生临时对象。不同的是,由于 obj 是一个右值引用,引用的对象是函数 getMyClass() 返回的临时对象,因此该临时对象的生命周期得到延长,所以我们可以在 MyClass &&obj = getMyClass() 语句结束后继续调用该对象的其它方法而不会发生任何问题。

三、移动语义

  上述代码中 3 次构造函数的调用,不难发现第二次和第三次的复制构造是影响性能的主要原因。在这个过程中都有临时对象参与进来,而临时对象本身只是做数据的复制。如果能将临时对象的内存直接转移到 obj 对象中,就能消除内存复制对性能的消耗。在 C++11 标准中引入了 移动语义,它可以帮助我们将临时对象的内存移动到 obj 对象中,以避免内存数据的复制。

#include <iostream>using namespace std;class MyClass 
{
private:int *p;public:MyClass(void);                          // 无参构造函数MyClass(const MyClass &other);          // 拷贝构造函数MyClass(MyClass &&other);               // 移动构造函数~MyClass(void);                         // 析构函数
};MyClass::MyClass(void) : p(new int(100))
{cout << "Constructor called" << endl;
}MyClass::MyClass(const MyClass &other) : p(new int(*other.p))
{cout << "Copy constructor called" << endl;
}MyClass::MyClass(MyClass &&other) : p(other.p) 
{cout << "Move constructor called" << endl;other.p = nullptr;
}MyClass::~MyClass(void) 
{cout << "Destructor called" << endl;delete p;
}MyClass getMyClass(void) 
{MyClass temp;return temp;                            // 返回一个临时对象,这是一个右值
}int main(void) 
{MyClass obj1 = getMyClass();return 0;
}

  在上面的代码中 MyClass 类中增加了构造函数 MyClass(MyClass&& other),它的形参是一个 右值引用 类型,称为 移动构造函数

  对于 复制构造函数 而言形参是一个 左值引用,也就是说函数的实参必须是一个具名的 左值,在复制构造函数中往往进行的是 深复制,即在不能破坏实参对象的前提下复制目标对象。而 移动构造函数 恰恰相反,它接受的是一个 右值,其核心思想是通过 转移实参对象的数据 以达成构造目标对象的目的,也就是说实参对象是会被修改的。

  运行程序可以发现,后面两次的构造函数变成了 移动构造函数,因为这两次操作中源对象都是 右值(临时对象),对于右值编译器会优先选择使用移动构造函数去构造目标对象。当移动构造函数不存在的时候才会退而求其次地使用复制构造函数。在移动构造函数中使用了指针转移的方式构造目标对象,所以整个程序的运行效率得到大幅提升。

四、完美转发

  完美转发(Perfect Forwarding)是 C++11 中引入的一个特性,它允许在函数模板中将参数连同其值类别(左值或右值)不变地转发给另一个函数。这意味着如果原始参数是一个左值,它将被转发为左值;如果原始参数是一个右值,它将被转发为右值。完美转发通常涉及到两个函数模板:std::forwardstd::movestd::forward 模板函数用于转发参数,而 std::move 用于将左值转换为右值引用。

#include <iostream>
#include <type_traits>using namespace std;
void printValue(int& value) 
{cout << "Lvalue reference to: " << value << endl;
}void printValue(int&& value) 
{cout << "Rvalue reference to: " << value << endl;
}template<typename T>
void forwardValue(T &&value) 
{printValue(std::forward<T>(value));
}int main(void) 
{int x = 42;forwardValue(x);                    // 转发左值forwardValue(42);                   // 转发右值forwardValue(std::move(x));         // 强制转为右值并转发return 0;
}

  在这个示例中,forwardValue 是一个模板函数,它接受一个通用引用参数 T&&。使用 std::forward<T>(value),我们可以将参数 value 连同其值类别完美地转发给 printValue() 函数。这样,printValue() 函数就能根据接收到的参数是左值还是右值来选择正确的重载版本。

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

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

相关文章

免费开源的Koodo Reader:轻松管理电子书并实现远程访问

文章目录 前言1. Koodo Reader 功能特点1.1 开源免费1.2 支持众多格式1.3 多平台兼容1.4 多端数据备份同步1.5 多功能阅读体验1.6 界面简洁直观 2. Koodo Reader安装流程2.1 安装Git2.2 安装Node.js2.3 下载koodo reader 3. 安装Cpolar内网穿透3.1 配置公网地址3.2 配置固定公网…

进程池的子进程的清理工作问题

首先进程池看看代码怎么写的 https://gitee.com/ljh0617/linux_test/blob/master/11-17/3.pipe_use/ProcessPool.cc 我们对子进程分配到的管道读文件描述符进行了重定向&#xff0c;让他改为从0读&#xff0c;这和清理工作无关&#xff0c;只是这么设计让子进程不再有键盘输入…

Java 多线程详细介绍

Java 多线程详细介绍 线程是多线程的支柱。我们生活在一个现实世界中&#xff0c;这个世界本身就被大量应用程序包围着。随着技术的进步&#xff0c;除非我们有效地引入多任务处理的概念&#xff0c;否则我们无法达到同时运行它们所需的速度。这是通过线程的概念实现的。 Java…

二叉树+树的OJ题讲解

求第K层节点个数 思路:走到K1就不走了,一次传回得到的值 #include<stdio.h> #include<stdlib.h> //树的定义 typedef int BTDataType; typedef struct BinaryTreeNode {BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right; }BTNode;//手…

Android kotlin之配置kapt编译器插件

配置项目目录下的gradle/libs.versions.toml文件&#xff0c;添加kapt配置项&#xff1a; 在模块目录下build.gradle.kt中增加 plugins {alias(libs.plugins.android.application)alias(libs.plugins.jetbrains.kotlin.android)// 增加该行alias(libs.plugins.jetbrains.kotl…

类和对象——拷贝构造函数,赋值运算符重载(C++)

1.拷⻉构造函数 如果⼀个构造函数的第⼀个参数是自身类类型的引用&#xff0c;且任何额外的参数都有默认值&#xff0c;则此构造函数也叫做拷贝构造函数&#xff0c;也就是说拷贝构造是⼀个特殊的构造函数。 // 拷贝构造函数//d2(d1) Date(const Date& d) {_year d._yea…

STM32G4的数模转换器(DAC)功能介绍

目录 概述 1 DAC介绍 1.1 功能 1.2 主要特征 1.3 DAC特性总结 ​2 DAC模块框架结构 3 DAC数据格式 3.1 单DAC通道 3.2 双通道数据格式 3.3 有符号、无符号数据 4 DAC数据转换 ​5 DAC输出电压 概述 本文主要介绍STM32G4的数模转换器&#xff08;DAC&#xff09;功能&a…

Pointnet++改进68:添加FFCM |融合傅里叶卷积

简介:1.该教程提供大量的首发改进的方式,降低上手难度,多种结构改进,助力寻找创新点!2.本篇文章对Pointnet++特征提取模块进行改进,加入,提升性能。3.专栏持续更新,紧随最新的研究内容。 目录 1.理论介绍 2.修改步骤 2.1 步骤一 2.2 步骤二 2.3 步骤三 1.理论介绍 …

Linux:解决远程X无法连通问题,X-Server开启TCP连接

一、问题分析 提前申明&#xff1a; 本次实验使用REHL 8 进行操作&#xff01; 客户机 A 为X-Client &#xff0c;即远程X的客户端。 服务机 B 为X-Server&#xff0c;即远程X的服务端。 问题的所有操作均在已经配置好Xorg的前提下进行的&#xff0c;不知道不配置会有什么影响&…

零基础Java第十九期:认识String(一)

目录 一、String的重要性 二、String的常用方法 2.1. 字符串构造 2.2. String对象的比较 2.3. 字符串查找 2.4. 转化 2.4. 字符串替换 2.5. 字符串拆分 2.6. 字符串截取 一、String的重要性 在C语言中已经涉及到字符串了&#xff0c;但是在C语言中要表示字符串只能…

HarmonyOS4+NEXT星河版入门与项目实战--------ArkTs语言与TypeScript语法

文章目录 1、ArkTs语言1、ArkTs 特点2、ArkTs与Javascript关系 2、TypeScript 语法 1、ArkTs语言 在html的开发中&#xff0c;实现一个页面元素&#xff0c;比如Button&#xff0c;往往包含了以下三种要素&#xff1a;JS、HTML、CSS。JS处理逻辑与响应、HTML 用来声明标签生成…

使用yak编写yakit漏洞检测插件

前言 在使用yakit进行编写yaml插件的时候遇到了yaml无法处理的情况&#xff0c;我不知道是不是yaml无法处理或者说是yakit和yaml的兼容还不够&#xff0c;面对变量的处理还是有些难受&#xff0c;于是花了点时间看了官网的yak语法的手册和其他人写的yak插件尝试使用yak语言来完…

信也科技和云杉网络的AI可观测性实践分享

1. 信也科技 2、云杉网络 2.1 中国移动

Blossom:开源私有部署的markdown笔记软件

在信息化、数字化时代&#xff0c;我们每个人的生活和工作都离不开笔记和知识管理。从简单的待办事项&#xff0c;到复杂的项目计划&#xff0c;再到存储大量个人知识的工具&#xff0c;如何选择一个高效、便捷且符合个人需求的笔记软件&#xff0c;成了许多人的难题。最近在逛…

Spring:DI依赖注入的方式

Spring为我们提供了两种注入方式&#xff0c;分别是: setter注入 简单类型引用类型 构造器注入 简单类型引用类型 setter注入 在bean中定义引用类型属性&#xff0c;并提供可访问的set方法配置中使用property标签ref属性注入引用类型对象 (1)项目中添加BookDao、BookDaoIm…

逆向攻防世界CTF系列37-crackme

逆向攻防世界CTF系列37-crackme 参考https://blog.csdn.net/xiao__1bai/article/details/120230397 nspack的壳&#xff0c;查了一下好像是北斗的一个壳 没找到什么脱壳软件&#xff0c;只能手动脱壳了 手动脱壳的最终要的是ESP定律 ESP定律的原理就是“堆栈平衡”原理 涉及…

按钮权限的操作方法

首先先在你的本地储存里边&#xff0c;加入一些你指定的字段 然后创建一个文件夹&#xff0c;在此文件夹下创建一个js文件&#xff0c;文件内容如下 在你所需要隐藏按钮的页面引入此js文件&#xff0c;并且通过 directives自定义指令绑定你的每一个按钮。在js文件中通过三个常量…

vscode 关闭绑定元素 隐式具有“any”类型这类错误

在vue的项目里面&#xff0c;经常看到any类型的报错&#xff0c;真的很烦的 在tsconfig.json中配置以下参数 “noImplicitAny”: false 就可以了 出现类型“never”上不存在属性“userName”。ts-plugin(2339) 配置该参数 modeuleResolution : node "compilerOptions&qu…

springboot 的 Profile

什么是 Profile &#xff1f; 应用所在的运行环境发生切换时&#xff0c;配置文件常常就需要随之修改。 Profile&#xff1a;——就是一组配置文件及组件的集合。 可以整个应用在不同的profile之间切换&#xff08;设置活动profile&#xff09;&#xff0c;整个应用都将使用该…

onvif协议相关:4.1.6 Digest方式云台控制启动

背景 关于onvif的其实很早之前我已经在专栏中写了不少了, 使用onvif协议操作设备 但最近有陆陆续续的粉丝问我, 希望我在写一些关于 onvif的设备自动发现、预置位跳转、云台操作的博客。 满足粉丝的需求,安排。 今天我们来实现 设备云台的控制(启动) 实现 1.在ONVIF Devi…