C++技能系列 ( 7 ) - 右值引用、移动语意、完美转发

news2024/12/27 11:41:58

在这里插入图片描述

现在的一切都是为将来的梦想编织翅膀,让梦想在现实中展翅高飞。
Now everything is for the future of dream weaving wings, let the dream fly in reality.

右值引用、移动语意、完美转发

  • 1、右值引用
  • 2、完美转发

1、右值引用

右值引用(rvalue reference)是 C++11 为了实现移动语意(move semantic)和完美转发(perfect forwarding)而提出来的。
右值引用,简单说就是绑定在右值上的引用。右值的内容可以直接移动(move)给左值对象,而不需要进行开销较大的深拷贝(deep copy)。
移动语义
下面这个例子:
v2 = v1 调用的是拷贝赋值操作符,v2 复制了 v1 的内容 —— 复制语义。
v3 = std::move(v1) 调用的是移动赋值操作符,将 v1 的内容移动给 v3 —— 移动语义。

  std::vector<int> v1{1, 2, 3, 4, 5}; 
  std::vector<int> v2; 
  std::vector<int> v3; 

  v2 = v1; 
  std::cout << v1.size() << std::endl;  // 输出 5
  std::cout << v2.size() << std::endl;  // 输出 5

  v3 = std::move(v1); // move
  std::cout << v1.size() << std::endl;  // 输出0
  std::cout << v3.size() << std::endl;  // 输出 5

为了实现移动语意,C++ 增加了与拷贝构造函数(copy constructor)和拷贝赋值操作符(copy assignment operator)对应的移动构造函数(move constructor)和移动赋值操作符(move assignment operator),通过函数重载机制来确定应该调用拷贝语意还是移动语意(参数是左值引用就调用拷贝语意;参数是右值引用就调用移动语意)。 再来看一个简单的例子:

#include <iostream>
#include <string>
#include <vector>

class Foo {
 public:
  // 默认构造函数
  Foo() { std::cout << "Default Constructor: " << Info() << std::endl; }

  // 自定义构造函数
  Foo(const std::string& s, const std::vector<int>& v) : s_(s), v_(v) {
    std::cout << "User-Defined Constructor: " << Info() << std::endl;
  }

  // 析构函数
  ~Foo() { std::cout << "Destructor: " << Info() << std::endl; }

  // 拷贝构造函数
  Foo(const Foo& f) : s_(f.s_), v_(f.v_) {
    std::cout << "Copy Constructor: " << Info() << std::endl;
  }

  // 拷贝赋值操作符
  Foo& operator=(const Foo& f) {
    s_ = f.s_;
    v_ = f.v_;
    std::cout << "Copy Assignment: " << Info() << std::endl;
    return *this;
  }

  // 移动构造函数
  Foo(Foo&& f) : s_(std::move(f.s_)), v_(std::move(f.v_)) {
    std::cout << "Move Constructor: " << Info() << std::endl;
  }

  // 移动赋值操作符
  Foo& operator=(Foo&& f) {
    s_ = std::move(f.s_);
    v_ = std::move(f.v_);
    std::cout << "Move Assignment: " << Info() << std::endl;
    return *this;
  }

  std::string Info() {
    return "{" + (s_.empty() ? "'empty'" : s_) + ", " +
           std::to_string(v_.size()) + "}";
  }

 private:
  std::string s_;
  std::vector<int> v_;
};

int main() {
  std::vector<int> v(1024);

  std::cout << "================ Copy =======================" << std::endl;
  Foo cf1("hello", v);
  Foo cf2(cf1);  // 调用拷贝构造函数
  Foo cf3;
  cf3 = cf2;  // 调用拷贝赋值操作符
  
  std::cout << "================ Move =========================" << std::endl;
  Foo f1("hello", v);
  Foo f2(std::move(f1));  // 调用移动构造函数
  Foo f3;
  f3 = std::move(f2);  // 调用移动赋值操作符
  return 0;
}

简单封装了一个类 Foo,重点是实现:
拷贝语意:拷贝构造函数 Foo(const Foo&) 、拷贝赋值操作符 Foo& operator=(const Foo&) 。
移动语意:移动构造函数 Foo(Foo&&) 、移动赋值操作符 Foo& operator=(Foo&&) 。
拷贝语意相信大部分人都比较熟悉了,也比较好理解。在这个例子中,每次都会拷贝 s_ 和 v_ 两个成员,最后 cf1、cf2、cf3 三个对象的内容都是一样的。 每次执行移动语意,是分别调用 s_ 和 v_ 的移动语意函数——理论上只需要对内部指针进行修改,所以效率较高。执行移动语意的代码片段了出现了一个标准库中的函数 std::move —— 它可以将参数强制转换成一个右值。本质上是告诉编译器,我想要 move 这个参数——最终能不能 move 是另一回事——可能对应的类型没有实现移动语意,可能参数是 const 的。 有一些场景可能拿到的值直接就是右值,不需要通过 std::move 强制转换,比如:

Foo GetFoo() {
  return Foo("GetFoo", std::vector<int>(11));
}
....
Foo f3("world", v3);
....
f3 = GetFoo(); // GetFoo 返回的是一个右值,调用移动赋值操作符

2、完美转发

C++ 通过了一个叫 std::forward 的函数模板来实现完美转发。这里直接使用 Effective Modern C++ 中的例子作为说明。在前面的例子上,我们增加如下的代码:

// 接受一个 const 左值引用
void Process(const Foo& f) {
  std::cout << "lvalue reference" << std::endl;
  // ...
}

// 接受一个右值引用
void Process(Foo&& f) {
  std::cout << "rvalue reference" << std::endl;
  // ...
}

template <typename T>
void LogAndProcessNotForward(T&& a) {
  std::cout << a.Info() << std::endl;
  Process(a); 
}

template <typename T>
void LogAndProcessWithForward(T&& a) {
  std::cout << a.Info() << std::endl;
  Process(std::forward<T>(a));
}

 LogAndProcessNotForward(f3);                         // 输出 lvalue reference
 LogAndProcessNotForward(std::move(f3));  // 输出 lvalue reference

 LogAndProcessWithForward(f3);                        // 输出 lvalue reference
 LogAndProcessWithForward(std::move(f3));  // 输出 rvalue reference

LogAndProcessNotForward(f3); 和 LogAndProcessWithForward(f3); 都输出 “lvalue reference”,这一点都不意外,因为 f3 本来就是一个左值。
LogAndProcessNotForward(std::move(f3)); 输出 “lvalue reference” 是因为虽然参数 a 绑定到一个右值,但是参数 a 本身是一个左值。
LogAndProcessWithForward(std::move(f3)); 使用了 std::forward 对参数进行转发,std::forward 的作用就是:当参数是绑定到一个右值时,就将参数转换成一个右值。
左值?右值?
到底什么时候是左值?什么时候是右值?是不是有点混乱? 在 C++ 中,每个表达式(expression)都有两个特性:
has identity? —— 是否有唯一标识,比如地址、指针。有唯一标识的表达式在 C++ 中被称为 glvalue(generalized lvalue)。
can be moved from? —— 是否可以安全地移动(编译器)。可以安全地移动的表达式在 C++ 中被成为 rvalue。
根据这两个特性,可以将表达式分成 4 类:
has identity and cannot be moved from - 这类表达式在 C++ 中被称为 lvalue。
has identity and can be moved from - 这类表达式在 C++ 中被成为 xvalue(expiring value)。
does not have identity and can be moved from - 这类表达式在 C++ 中被成为 prvalue(pure rvalue)。
does not have identity and cannot be moved -C++ 中不存在这类表达式。
简单总结一下这些 value categories 之间的关系:
可以移动的值都叫 rvalue,包括 xvalue 和 prvalue。
有唯一标识的值都叫 glvalue,包括 lvalue 和 xvalue。
std::move 的作用就是将一个 lvalue 转换成 xvalue。

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

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

相关文章

Redux的纯函数、中间件

当我们的应用随着业务的发展&#xff0c;变得越来越复杂的时候&#xff0c;组件之间的状态也是越来越复杂。 reducer函数 之前说过redux中的action&#xff0c;是用来描述一种变化。但是完成变化并且生辰新的数据数据状态的是reducer方法。 reducer方法&#xff0c;必须是纯…

SQL注入攻击与防护

目录 一、SQL注入攻击概述 1.1 SQL注入概念 1.1.1 标准查询过程 1.1.2 SQL注入定义 1.2 SQL注入根本原因 1.3 SQL注入条件 1.4 SQL注入防范 1.4.1 根本原因&#xff1a;过滤不严 1.4.2 安全设计原则&#xff1a;数据与代码分离 1.5 SQL注入流程 1.6 SQL注入分类 1.…

vue3+vite+ts项目配置开发环境和生产环境 打包命令配置

开发环境和生产环境的配置和打包方式有所不同&#xff0c;下面是基于vue3vitets项目的开发环境和生产环境配置及打包方式的详细说明。 开发环境配置 开发环境的配置主要是为了方便开发者进行调试和测试&#xff0c;以下是开发环境的配置步骤&#xff1a; 1.1 安装依赖 首先…

DAY27:回溯算法(二)组合问题及其优化

文章目录 77.组合&#xff08;一定要注意逻辑问题&#xff09;思路for循环嵌套的情况回溯算法模拟for循环K层嵌套 回溯法步骤伪代码完整版debug测试逻辑问题&#xff1a;没有输出逻辑问题&#xff1a;为什么是递归传入i1而不是startIndex1&#xff1f;重要&#xff1a;为什么会…

Java-API简析_java.lang.CharSequence接口(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/131318474 出自【进步*于辰的博客】 其实我的【Java-API】专栏内的博文对大家来说意义是不大的。…

Unreal 5 实现场景

如果你拿到了一个新的场景&#xff0c;想将此场景应用到游戏当中&#xff0c;首先需要给敌人增加ai移动路径&#xff0c;需要添加导航体积 添加导航模型包围体积 添加了体积以后&#xff0c;设置包围盒的大小&#xff0c;将敌人可以行进的区域给区分出来&#xff0c;然后按键盘…

PyCharm2023开发工具activice教程(包含工具link)

PyCharm2023 前言1. 下载工具2. 选择安装方法33. 填入active code4. 效果如下 前言 PyCharm是一款由JetBrains开发的强大的Python集成开发环境&#xff08;IDE&#xff09;。它提供了丰富的功能和工具&#xff0c;旨在提高Python开发者的生产力和效率。 以下是PyCharm的一些主…

Linux操作系统体系结构 ( 3 ) -【Linux通信架构系列 】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everything is for the…

FTP服务器

文章目录 FTP服务器FTP的数据传输原理FTP的功能简介不同等级的用户身份命令记录与日志文件记录限制用户活动的目录 FTP的工作流程与使用到的端口FTP主动式连接FTP被动式连接 vsftpd服务器基础设置为什么使用vsftpd所需要的软件以及软件结构vsftpd.conf 配置值说明与服务器环境比…

【入门向】CV 小白如何入门?人脸识别教程带你学习计算机视觉

导言 计算机视觉作为人工智能领域的一个重要分支&#xff0c;旨在让计算机能够理解和解释图像和视频数据。而OpenCV作为一款开源的计算机视觉库&#xff0c;为开发者提供了丰富的工具和函数&#xff0c;用于处理图像、视频、对象检测、特征提取等任务。对于初学者来说&#xf…

chatgpt赋能python:如何在Python中捕获kill信号

如何在Python中捕获kill信号 在编写Python代码时&#xff0c;我们可能需要处理一些长时间运行的进程。有时候&#xff0c;我们会在运行这些进程时使用kill命令杀死它们。然而&#xff0c;Python进程是否可以捕获kill信号呢&#xff1f;答案是肯定的。 在本文中&#xff0c;我…

基于pyqt5、mysql、yolov7、chatgpt的小麦病害检测系统v1.0

基于pyqt5、mysql、yolov7、chatgpt的小麦病害检测系统设计与实现 一、界面设计1.1安装pyqt51.2创建用户子窗体1.3创建管理员主窗体1.4创建管理员子窗体1.5创建系统登陆界面 二、环境搭建2.1pyqt5工具配置2.2mysql5.7安装 三、编程实现3.1初始化数据库3.2创建用户数据库sdk文件…

chatgpt赋能python:Python如何快速提取指定行和列的数据?

Python如何快速提取指定行和列的数据&#xff1f; 在进行数据分析和处理时&#xff0c;常常需要从海量数据中筛选出所需的数据。这时&#xff0c;Python是一款非常强大的工具&#xff0c;可以方便地进行大规模数据清洗和筛选。本文将介绍如何使用Python快速提取指定行和列的数…

【JVM篇】手撸上万字带你吃透“垃圾回收”

前言&#xff1a;大家好&#xff0c;我是TwosJel&#xff0c;一名21级的本科生(*^▽^*)&#xff0c;最近二刷了《深入理解Java虚拟机》&#xff0c;因此想写一篇关于垃圾回收的随笔&#xff0c;于是便有了这篇文章❥(^_-)。 个人主页&#xff1a;TwosJel 个人介绍&#xff1a…

JWT --- 入门学习

1.常见的认证机制 basic auth &#xff1a; 每次请求都会携带用户的username&#xff0c;password&#xff0c;易被黑客拦截。 Cookie auth : 我们请求服务器&#xff0c;创建一个session对象,客户端创建cookie对象。客户端每次访问&#xff0c;携带cookie对象。 (在当今&…

chatgpt赋能python:Python排队:提高效率、优化流程的神器

Python排队&#xff1a;提高效率、优化流程的神器 随着科技的不断进步&#xff0c;排队已经成为了现代生活中不可避免的一部分。在各个行业中&#xff0c;排队都是必须考虑的问题&#xff0c;包括餐馆、医院、机场和银行等等。针对排队问题&#xff0c;我们可以使用Python编程…

使用Vue + FormData + axios实现图片上传功能实战

前言 上节回顾 上一小节中&#xff0c;我们添加了Vue-router的路有数据&#xff0c;这些数据都将是后续实战课程中的真实路由数据了。同时引入了ElementUI的el-menu做为左侧菜单的组件&#xff0c;但本专栏的特点就是遇到第三方功能和组件&#xff0c;自己尽量也要实现一遍&a…

蓝牙ATT协议介绍

介绍 ATT&#xff0c;Attribute Protocol&#xff0c;用于发现、读、写对端设备的协议(针对BLE设备) ATT允许蓝牙远程设备&#xff08;比如遥控器&#xff09;作为服务端提供拥有关联值的属性集&#xff0c;让作为客户端的设备&#xff08;比如手机、电视&#xff09;来发现、…

【软件工程】软件工程期末考试试卷

瀑布模型把软件生命周期划分为八个阶段&#xff1a;问题的定义、可行性研究、软件需求分析、系统总体设计、详细设计、编码、测试和运行、维护。八个阶段又可归纳为三个大的阶段&#xff1a;计划阶段、开发阶段和( C)。 A、详细计划 B、可行性分析 C、 运行阶段 D、 测试与排…

JavaScript中的CRUD操作指南示例 - 用DHTMLX创建医院管理系统!

创建、读取、更新和删除(CRUD)是现代web和移动应用程序执行的四个基本功能。然而这些函数是如何产生的&#xff0c;它们到底是做什么的&#xff1f; 在本文中&#xff0c;我们将简要介绍CRUD的含义以及它何时被引入编程的。文中我们还将使用用于医院管理的JavaScript演示应用程…