移动语义和完美转发

移动语义和完美转发

移动语义

移动语义是 C++11 引入的一项特性,通过右值引用(Rvalue Reference)实现。它的目标是提高对于临时对象或即将销毁的对象的效率,避免不必要的深拷贝,而是在必要的时候将资源所有权从一个对象转移到另一个对象。

移动语义的关键在于对于右值引用的使用,它使用 && 表示。在移动语义中,有两个主要的概念:移动构造函数和移动赋值运算符。

移动构造函数

移动构造函数是一种特殊的构造函数,它允许将一个对象的资源(例如动态分配的内存、文件句柄等)从一个对象移动到另一个对象,而不是进行深拷贝。

代码示例:

class MyClass {
public:// 移动构造函数MyClass(MyClass&& other) noexcept {// 移动资源的所有权resource = other.resource;other.resource = nullptr;}// 其他成员函数和构造函数省略private:int* resource;  // 假设 resource 是需要移动的资源
};
移动赋值运算符

移动赋值运算符用于将一个对象的资源移动到另一个对象,类似于移动构造函数,但是它在对象已经存在的情况下使用

class MyClass {
public:// 移动赋值运算符MyClass& operator=(MyClass&& other) noexcept {if (this != &other) {// 释放当前对象的资源delete resource;// 移动资源的所有权resource = other.resource;other.resource = nullptr;}return *this;}// 其他成员函数和构造函数省略private:int* resource;  // 假设 resource 是需要移动的资源
};
move函数

move 是 C++ 标准库中定义的一个函数模板,位于头文件 <utility> 中。它用于将一个左值转换为右值引用,主要用于支持移动语义。move 并不实际进行移动操作,它只是通过类型转换告诉编译器,我们允许对这个左值进行移动操作。

move函数的实现代码:

namespace std {template <typename T>constexpr remove_reference_t<T>&& move(T&& arg) noexcept {return static_cast<remove_reference_t<T>&&>(arg);}
}

std::move 接受一个模板参数 T,并返回一个 T&& 类型的右值引用。它实际上是通过 static_cast 进行类型转换的。在右值引用&&的特性中介绍到, T&&作为函数参数, 可以推到为左值引用

这里使用了 static_castT&& 转换为 remove_reference_t<T>&&remove_reference_t 是一个模板元编程工具,用于移除引用。这个转换告诉编译器,我们希望将传入的左值引用 arg 转换为右值引用。

综合例子
#include <iostream>
#include <string>class MyString {
public:// 普通构造函数MyString(const char* data) : size(std::strlen(data)), str(new char[size + 1]) {std::cout << "普通构造函数" << std::endl;strcpy_s(str, size + 1, data);}// 移动构造函数MyString(MyString&& other) noexcept : size(other.size), str(other.str) {std::cout << "移动构造函数" << std::endl;other.size = 0;other.str = nullptr;}// 移动赋值运算符MyString& operator=(MyString&& other) noexcept {std::cout << "移动赋值运算" << std::endl;if (this != &other) {delete[] str;size = other.size;str = other.str;other.size = 0;other.str = nullptr;}return *this;}// 打印字符串void print() const {std::cout << "String: " << (str ? str : "null") << std::endl;}~MyString() {delete[] str;}private:size_t size;char* str;
};int main() {MyString a("Hello");  // 普通构造函数a.print();MyString b(std::move(a));  // 移动构造函数a.print(); // nullb.print();// MyString c = b; // error 移动构造函数, = 右边应该是一个右值MyString c = std::move(b); // 移动构造a = MyString("World");  // 普通构造函数 + 移动赋值运算符// MyString("World") 属于是一个用普通构造创建的一个右值, 通过=赋值给aa.print();return 0;
}

输出结果为:

普通构造函数
String: Hello
移动构造函数
String: null
String: Hello
移动构造函数
普通构造函数
移动赋值运算
String: World
MyString b(std::move(a));  // 移动构造函数
a.print(); // null

a已经将资源转移给b了, 打印a输出null

完美转发

概述

完美转发是 C++11 引入的一个重要特性,它允许在模板中精确地保持函数参数的值类别(左值还是右值),并将它们传递到其他函数。完美转发通常用于实现泛型代码,特别是在编写通用函数模板时非常有用。

完美转发的核心在于使用通用引用(Universal Reference)和引用折叠。通用引用的语法是 T&&,其中 T 是模板参数。引用折叠是指在模板类型推导或类型声明的过程中,两个引用(无论是左值引用还是右值引用)可能折叠成一个引用。

代码示例:

#include <iostream>
#include <utility>// 用于演示的一个简单处理函数
void process(int& x) {std::cout << "Lvalue reference: " << x << std::endl;
}void process(int&& x) {std::cout << "Rvalue reference: " << x << std::endl;
}// 完美转发函数模板
template <typename T>
void forwarder(T&& arg) {// 在这里可以根据 arg 的值类别进行处理process(std::forward<T>(arg));
}int main() {int x = 42;forwarder(x);        // 传递左值forwarder(123);      // 传递右值return 0;
}

输出为:

Lvalue reference: 42
Rvalue reference: 123

在这个例子中,forwarder 是一个接受通用引用的函数模板,它调用 process 函数并使用 std::forward 来完美转发参数。process 函数有两个重载版本,分别接受左值引用和右值引用。

通过 forwarder(x) 调用时,T 被推导为 int&,从而调用了接受左值引用的 process 版本。通过 forwarder(123) 调用时,T 被推导为 int,从而调用了接受右值引用的 process 版本。(详细说明可以看下面的forward函数)

在实际使用中,完美转发通常结合模板参数包(Template Parameter Pack)和 std::forward 使用,以处理任意数量和类型的参数。这使得编写通用的函数模板成为可能,可以适应各种情况。

forward函数

这里从另一个角度说一下完美转发和forward函数

右值引用类型是独立于值的,一个右值引用作为函数参数的形参时,在函数内部转发该参数给内部其他函数时,它就变成一个左值,并不是原来的类型了。如果需要按照参数原来的类型转发到另一个函数,可以使用C++11提供的std::forward()函数,该函数实现的功能称之为完美转发。

函数原型如下:

// 函数原型
template <class T> T&& forward (typename remove_reference<T>::type& t) noexcept;
template <class T> T&& forward (typename remove_reference<T>::type&& t) noexcept;// 精简之后的样子
std::forward<T>(t);

重点:

  • 当T为左值引用类型时,t将被转换为T类型的左值
  • 当T不是左值引用类型时,t将被转换为T类型的右值

代码示例:

#include <iostream>
using namespace std;template<typename T>
void printValue(T& t)
{cout << "l-value: " << t << endl;
}template<typename T>
void printValue(T&& t)
{cout << "r-value: " << t << endl;
}template<typename T>
void testForward(T && v)
{printValue(v);printValue(move(v));printValue(forward<T>(v));cout << endl;
}int main()
{testForward(520);int num = 1314;testForward(num);testForward(forward<int>(num));testForward(forward<int&>(num));testForward(forward<int&&>(num));return 0;
}

输出结果:

l-value: 520
r-value: 520
r-value: 520l-value: 1314
r-value: 1314
l-value: 1314l-value: 1314
r-value: 1314
r-value: 1314l-value: 1314
r-value: 1314
l-value: 1314l-value: 1314
r-value: 1314
r-value: 1314
  1. testForward(520);函数的形参为未定引用类型T&&,实参为右值,初始化后被推导为一个右值引用
    printValue(v);已命名的右值v,编译器会视为左值处理,实参为左值
    printValue(move(v));已命名的右值编译器会视为左值处理,通过move又将其转换为右值,实参为右值
    printValue(forward(v));forward的模板参数为右值引用,最终得到一个右值,实参为右值

  2. testForward(num);函数的形参为未定引用类型T&&,实参为左值,初始化后被推导为一个左值引用
    printValue(v);实参为左值
    printValue(move(v));通过move将左值转换为右值,实参为右值
    printValue(forward(v));forward的模板参数为左值引用,最终得到一个左值引用,实参为左值

  3. testForward(forward(num));forward的模板类型为int,最终会得到一个右值,函数的形参为未定引用类型T&&被右值初始化后得到一个右值引用类型
    printValue(v);已命名的右值v,编译器会视为左值处理,实参为左值
    printValue(move(v));已命名的右值编译器会视为左值处理,通过move又将其转换为右值,实参为右值
    printValue(forward(v));forward的模板参数为右值引用,最终得到一个右值,实参为右值

  4. testForward(forward<int&>(num));forward的模板类型为int&,最终会得到一个左值,函数的形参为未定引用类型T&&被左值初始化后得到一个左值引用类型
    printValue(v);实参为左值
    printValue(move(v));通过move将左值转换为右值,实参为右值
    printValue(forward(v));forward的模板参数为左值引用,最终得到一个左值,实参为左值

  5. testForward(forward<int&&>(num));forward的模板类型为int&&,最终会得到一个右值,函数的形参为未定引用类型T&&被右值初始化后得到一个右值引用类型
    printValue(v);已命名的右值v,编译器会视为左值处理,实参为左值
    printValue(move(v));已命名的右值编译器会视为左值处理,通过move又将其转换为右值,实参为右值
    printValue(forward(v));forward的模板参数为右值引用,最终得到一个右值,实参为右值

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

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

相关文章

B Label, BL Label 指令

B Label, BL Label 跳转到标号Label 处&#xff0c;B跳转指令的跳转范围大小为[0,32MB], 可以往前跳&#xff0c;也可以往后条&#xff0c;无条件跳转指令B主要用在循环&#xff0c;分之结构的汇编程序中&#xff0c;使用示例如下。 CMP R2, #0 REQ label 若R2 0,则跳转到labe…

mfc140u.dll丢失的解决方法,以及mfc140u.dll解决方法的优缺点

在使用电脑过程中&#xff0c;有时会遇到一些与动态链接库文件&#xff08;DLL&#xff09;相关的错误。其中&#xff0c;mfc140u.dll丢失的错误是较为常见的一种。当这个关键的mfc140u.dll文件丢失或损坏时&#xff0c;可能会导致某些应用程序无法正常运行。在本文中&#xff…

linux清理僵尸进程

当你top看到这个&#xff0c;或者按M后看到内存吃的很多&#xff0c;那你看下有没有&#x1f9df; 二选一查看是什么进程 ps aux | egrep "Z|defunct" ps -aux | awk {if($8"Z"){print $2,$11}}没用直接杀杀杀 kill -9 查到的PID号可中断下载文件 wget…

AlmaLinux download

前言 一个开源的、社区拥有和管理的、永远免费的企业级Linux发行版&#xff0c;专注于长期稳定性&#xff0c;提供一个健壮的生产级平台。AlmaLinux操作系统是1:1二进制兼容RHEL和pre-Stream CentOS。 AlmaLinux download VersionAlmaLinux downloadAlmaLinux backup阿里云…

webshell免杀之传参方式

1.Cookie 由于Cookie基本上是每个web应用都需要使用到的&#xff0c;php应用在默认情况下&#xff0c;在Cookies请求头中会存在一个PHPSESSIDxxxx这样的cookie&#xff0c;其实这个就可以成为我们的传参位置 使用burp抓包将内容改成base64加密后的命令 可以看到已经执行成功了…

BeautifulReport测试报告框架

BeautifulReport测试报告框架 ##执行文件excute_case.py import unittest import os, datetime from BeautifulReport import BeautifulReportroot_dir os.path.dirname(os.path.abspath(__file__)).replace(\\, /) print(root_dir路径,root_dir) #test_dir root_dir /test…

c语言从入门到实战——回调函数与qsort的讲解和模拟实现

回调函数与qsort的讲解和模拟实现 前言1. 回调函数是什么&#xff1f;2. qsort2.1 使用qsort函数排序整型数据2.2 使用qsort排序结构数据 3. qsort函数的模拟实现 前言 回调函数是一个函数&#xff0c;它作为参数传递给另一个函数&#xff0c;并且能够在该函数内部被调用。在C…

Nginx 可视化管理平台:nginx-proxy-manager

本心、输入输出、结果 文章目录 Nginx 可视化管理平台:nginx-proxy-manager前言nginx-proxy-managernginx-proxy-manager 特性快速开始使用 Docker 网络开启 Docker 健康检查相关可视化页面相关链接弘扬爱国精神Nginx 可视化管理平台:nginx-proxy-manager 编辑:简简单单 Onl…

Vue 路由器传参和取值 路径参数

Query参数取值 http://www.csdn/index?id10&age123 取值 this.$route.query.id//获取id值 this.$route.query.age//获取age值传值 <router-link :to"{path:/index,query:{ id:10,age:123 }}"></router-link>携带参数跳转到指定路由 this.$ro…

深入解析SSD Wear Leveling磨损均衡技术:如何让你的硬盘更长寿?

SSD的存储介质是什么&#xff0c;它就是NAND闪存。那你知道NAND闪存是怎么工作的吗&#xff1f;其实&#xff0c;它就是由很多个晶体管组成的。这些晶体管里面存储着电荷&#xff0c;代表着我们的二进制数据&#xff0c;要么是“0”&#xff0c;要么是“1”。NAND闪存原理上是一…

【DevOps】Git 图文详解(五):远程仓库

Git 图文详解&#xff08;五&#xff09;&#xff1a;远程仓库 1.远程用户登录1.1 &#x1f511; 远程用户登录&#xff1a;HTTS1.2 &#x1f511; 远程用户登录&#xff1a;SSH 2.远程仓库指令 &#x1f525;3.推送 push / 拉取 pull4.fetch 与 pull 有什么不同 &#xff1f; …

【汇编】“转移”综述、操作符offset、jmp指令

文章目录 前言一、转移综述1.1 :背景&#xff1a;1.2 转移指令1.3 转移指令的分类按转移行为根据指令对IP修改的范围不同 二、操作符offset2.1 offset操作符是干什么的&#xff1f;标号是什么&#xff1f; 2.2 nop是什么&#xff1f; 三、jmp指令3.1 jmp指令的功能3.2 jmp指令&…

【目标测距】雷达投影测距

文章目录 前言一、读取点云二、点云投影图片三、读取检测信息四、点云投影测距五、学习交流 前言 雷达点云投影相机。图片目标检测&#xff0c;通过检测框约束等等对目标赋予距离。计算消耗较大&#xff0c;适合离线验证操作。在线操作可以只投影雷达检测框。 一、读取点云 py…

关于node安装和nvm安装的问题

node 1.已经自定义路径安装了node&#xff0c;但是在cmd输入node -v显示不是内部命令 路径问题&#xff1a;确保 Node.js 已经被添加到了系统的环境变量 PATH 中。PATH 环境变量包含了操作系统用来查找命令的位置。你可以通过以下步骤检查 Node.js 是否已被添加到 PATH&#x…

校园报修抢修小程序系统开发 物业小区报修预约上门维修工单系统

开发的功能模块有&#xff1a; 1.报修工单提交&#xff1a;学生、教职员工等可以使用小程序提交报修请求。这通常包括选择报修的问题类型&#xff08;如水漏、电器故障、照明问题等&#xff09;&#xff0c;地点&#xff0c;报修联系人&#xff0c;联系电话等&#xff0c;并提供…

【5k字长文 | Vue学习笔记】#1 认识Vue对象和基础语法

Vue是一个非常流行的渐进式JavaScript框架&#xff0c;渐进式指的是自底向上&#xff0c;从小组件逐渐向上构成整个项目&#xff0c;渐进式还可以理解为&#xff1a;用什么就拿什么&#xff0c;每个组件只做自己的事&#xff0c;尽可能解耦合。 本节我们将学习简单的Vue实例&a…

Thales安全解决方案:国家网络安全的关键

随着信息技术的飞速发展&#xff0c;网络安全问题日益凸显。在这个背景下&#xff0c;Thales安全解决方案正成为提高国家网络安全的关键。本文将探讨Thales安全解决方案如何为国家网络安全保驾护航。 一、Thales安全解决方案概述 Thales安全解决方案是一种全方位的网络安全防护…

upload-labs关卡11(双写后缀名绕过)通关思路

文章目录 前言一、回顾前几关知识点二、靶场第十一关通关思路1、看源代码2、bp抓包双写后缀名绕过3、检查文件是否成功上传 总结 前言 此文章只用于学习和反思巩固文件上传漏洞知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的平台&#xff0c;不能随意去尚未授权的…

细思极恐!5秒钟克隆你的声音

Mocking Bird 是开发者 babysor 开源的比较火的 AI 拟声开源项目&#xff0c;目前在 GitHub 已经获得了 32K 的 Star&#xff0c;它能在 5 秒内克隆你的声音并生成任意语音内容&#xff0c;支持中文普通话。 01 功能特性 支持中文普通话拟声&#xff0c;并且在多个中文数据集…

小程序授权获取头像

wxml <view class"header"><text>头像</text><button class"butt" plain"true" open-type"chooseAvatar" bind:chooseavatar"chooseAvatar"><image src"{{HeadUrl}}" mode"&quo…