C++的移动语义和完美转发

参考《现代C++语言核心特性解析》

移动语义

C++11新特性的std::move()用于将一个左值转换为右值引用。它并不是实际移动或复制数据,而是通过将一个左值强制转换为一个右值引用来实现对对象的转移。这个特性在C++11中引入,用于优化对象移动操作的效率。

我们知道,右值引用只能引用右值,如果尝试绑定左值就会编译错误。

int i = 0;
int &&k = i;	// 编译错误

在C++11标准中可以在不创建临时值的情况下显式地将左值通过static_cast转换为将亡值,通过值类别的内容我们知道将亡值属于右值,所以可以被右值引用绑定。值得注意的是,由于转换的并不是右值,因此它依然有着和转换之前相同的生命周期和内存地址,例如:

int i = 0;
int &&k = static_cast<int&&>(i);

既然这个转换既不改变生命周期,也不改变内存地址,那它存在的意义是什么?实际上它最大的作用是让左值使用移动语义。

举例:

#include <iostream>class BigMemoryPool
{
public:static const int PoolSize = 4096;BigMemoryPool() : pool_(new char[PoolSize]){std::cout << "普通构造函数" << std::endl;}~BigMemoryPool(){if (pool_ != nullptr){delete[] pool_;}}BigMemoryPool(BigMemoryPool &&other) : pool_(new char[PoolSize]){std::cout << "移动构造函数" << std::endl;pool_ = other.pool_;other.pool_ = nullptr;}BigMemoryPool(const BigMemoryPool &other) : pool_(new char[PoolSize]){std::cout << "拷贝构造函数" << std::endl;memcpy(pool_, other.pool_, PoolSize);}private:char *pool_;
};BigMemoryPool get_pool(const BigMemoryPool &pool)
{return pool;
}BigMemoryPool make_pool()
{BigMemoryPool pool;return get_pool(pool);
}int main()
{BigMemoryPool my_pool1;BigMemoryPool my_pool2 = my_pool1;BigMemoryPool my_pool3 = static_cast<BigMemoryPool &&>(my_pool1);return 0;
}

在这段代码中,my_pool1是一个BigMemoryPool类型的对象,也是一个左值,所以用它去构造my_pool2的时候调用的是复制构造函数。为了让编译器调用移动构造函数构造my_pool3,这里使用了static_cast<BigMemoryPool &&>(my_pool1)将my_pool1强制转换为右值(也是将亡值,为了叙述思路的连贯性后面不再强调)。由于调用了移动构造函数,my_pool1失去了自己的内存数据,后面的代码也不能对my_pool1进行操作了。

结果输出:

PS C:\Users\zh'n\Desktop\新建文件夹> g++ -std=c++11 -fno-elide-constructors main.cpp -o main
PS C:\Users\zh'n\Desktop\新建文件夹> ./main
普通构造函数
拷贝构造函数
移动构造函数

但是这个示例中把my_pool1这个左值转换成my_pool3这个左值似乎没有什么意义,而且程序员如果再次去访问my_pool1还会引发未定义行为。

正确的使用场景是在一个右值被转换为左值后需要再次转换为右值,最典型的例子是一个右值作为实参传递到函数中。我们在讨论左值和右值的时候曾经提到过,无论一个函数的实参是左值还是右值,其形参都是一个左值,即使这个形参看上去是一个右值引用,例如:

void move_pool(BigMemoryPool &&pool)
{std::cout << "call move_pool" << std::endl;BigMemoryPool my_pool(pool);
}int main()
{move_pool(make_pool());
}

结果输出:

PS C:\Users\zh'n\Desktop\新建文件夹> g++ -std=c++11 -fno-elide-constructors main.cpp -o main
PS C:\Users\zh'n\Desktop\新建文件夹> ./main
普通构造函数
拷贝构造函数
移动构造函数
call move_pool
拷贝构造函数

代码中,make_pool()返回的是一个临时对象,也是一个右值,move_pool的参数是一个右值引用,但是在使用形参pool去构造my_pool时调用的是拷贝构造函数。如果我们想调用移动构造函数的话,需要把形参pool强制转换为右值。

void move_pool(BigMemoryPool &&pool)
{std::cout << "call move_pool" << std::endl;BigMemoryPool my_pool = static_cast<BigMemoryPool &&>(pool); // 1
}

结果输出:

PS C:\Users\zh'n\Desktop\新建文件夹> g++ -std=c++11 -fno-elide-constructors main.cpp -o main
PS C:\Users\zh'n\Desktop\新建文件夹> ./main
普通构造函数
拷贝构造函数
移动构造函数
call move_pool
移动构造函数

请注意,在这个场景下强制转换为右值就没有任何问题了,因为move_pool函数的实参是make_pool返回的临时对象,当函数调用结束后临时对象就会被销毁,所以转移其内存数据不会存在任何问题。

在C++11的标准库中还提供了一个函数模板std::move帮助我们将左值转换为右值,这个函数内部也是用static_cast做类型转换。只不过由于它是使用模板实现的函数,因此会根据传参类型自动推导返回类型,省去了指定转换类型的代码。另一方面从移动语义上来说,使用std::move函数的描述更加准确。所以建议读者使用std::move将左值转换为右值而非自己使用static_cast转换,例如:

void move_pool(BigMemoryPool &&pool)
{std::cout << "call move_pool" << std::endl;BigMemoryPool my_pool(std::move(pool)); // 1
}

总结:

std::move()内部是用static_cast做类型转换,只不过它是使用模板实现的函数,因此会根据传参类型自动推导返回值类型,省去了指定类型的代码。如果使用std::move()将一个左值转换为右值并赋值给其他对象后,这个对象就会被销毁,所以在函数调用过程中,创建N个对象实际上只是把第一个对象的内存不断的转移,类似层层递归。 这样做的好处就是省去了创建对象的开销,并且在对象副本庞大的情况下节省了大量时间。

完美转发

在了解完美转发之前,先了解一下什么是万能引用和引用折叠。

我们知道常量左值引用可以引用左值,也可以引用右值,是一个几乎的万能引用,但是因为它的常量性导致使用受限制。

在C++11中有一个“万能引用”,例如:

void foo(int &&i){} // 右值引用template<class T>
void bar(T &&t){} // 万能引用int get_val(){return 5;}
int &&x = get_val(); // 右值引用
auto &&x = get_val(); // 万能引用

我们可以发现,只要是自动类型推导的引用就是万能引用。在这个推导过程中,源对象是左值,那就推导为左值引用,源对象是右值,那就推导为右值引用。

万能引用能如此灵活地引用对象,实际上是因为在C++11中添加了一套引用叠加推导的规则——引用折叠。在这套规则中规定了在不同的引用类型互相作用的情况下应该如何推导出最终类型。

在这里插入图片描述
举例说明:

int i = 42;
const int j = 11;
bar(i);
bar(j);
bar(get_val());auto &&x = i;
auto &&y = j;
auto &&z = get_val();

在bar(i);中i是一个左值,所以T的推导类型结果是int&,根据引用折叠规则int& &&的最终推导类型为int&,于是bar函数的形参是一个左值引用。而在bar(get_val());中get_val返回的是一个右值,所以T的推导类型为非引用类型int,于是最终的推导类型是int&&,bar函数的形参成为一个右值引用。

完美转发的用途
看一个常规的转发函数模板

#include <iostream>
#include <string>
#include <typeinfo>template<class T>
void show_type(T t)
{std::cout << typeid(t).name() << std::endl;
}template<class T>
void normal_forwarding(T t)
{show_type(t);
}int main()
{std::string s = "hello world";normal_forwarding(s);
}// 输出:Ss

normal_forwarding函数可以完成字符串的转发任务,但是它的效率很慢。首先它的参数是值传递,那么在转发过程中就会发生一次临时对象的复制。其中一个解决方法就是把void normal_forwarding(T t)换成void normal_forwarding(T& t),通过引用传递,但这是一个左值引用,如果参数是一个右值就会编译失败。

std::string get_string()
{return "hi world";
}normal_forwarding(get_string());    // 编译失败

但是常量左值可以引用右值,可以解决这个问题,但引来的新问题是常量左值引用具有常量性,使得对象不可以被修改。

所以万能引用的诞生解决了这个问题。

对于万能引用来说,如果实参是一个左值,那么形参会被推导为左值引用、如果实参是一个右值,那么形参会被推导为右值引用。

#include <iostream>
#include <string>template<class T>
void show_type(T t)
{std::cout << typeid(t).name() << std::endl;
}template<class T>
void perfect_forwarding(T &&t)	// 万能引用
{show_type(static_cast<T&&>(t));
}std::string get_string()
{return "hi world";
}int main()
{std::string s = "hello world";perfect_forwarding(s);perfect_forwarding(get_string());
}

和移动语义的情况一样,显式使用static_cast类型转换进行转发不是一个便捷的方法。在C++11的标准库中提供了一个std::forward函数模板,在函数内部也是使用static_cast进行类型转换,只不过使用std::forward转发语义会表达得更加清晰,std::forward函数模板的使用方法也很简单:

template<class T>
void perfect_forwarding(T &&t)
{show_type(std::forward<T>(t));
}

请注意std::move和std::forward的区别,其中std::move一定会将实参转换为一个右值引用,并且使用std::move不需要指定模板实参,模板实参是由函数调用推导出来的。而std::forward会根据左值和右值的实际情况进行转发,在使用的时候需要指定模板实参。

完整示例:

#include <iostream>
#include <string>
#include <typeinfo>template <class T>
void show_type(T t)
{std::cout << typeid(t).name() << std::endl;
}template <class T>
void perfect_forwarding(T &&t)
{show_type(std::forward<T>(t));
}int main()
{std::string s = "hello world";perfect_forwarding(s);	// 实参是左值perfect_forwarding(1.0); // 实参是右值
}// 输出
// Ss
// d

总结

完美转发允许将函数的参数(包括左值和右值)转发给其他函数,同时保持原始参数的值不变,这样可以实现高效的函数调用。

#include <iostream>
#include <utility>template <typename T>
void process(T &i)
{std::cout << "L-value: " << i << std::endl;
}template <typename T>
void process(T &&i)
{std::cout << "R-value: " << i << std::endl;
}template <typename T>
void forwarder(T &&t)
{process(std::forward<T>(t));
}int main()
{int a = 42;forwarder(a); // L-value: 42forwarder(7.1); // R-value: 7return 0;
}// 输出
// L-value: 42
// R-value: 7.1

在上面的示例中,forwarder函数使用了完美转发,它接受一个泛型类型的参数T&& t,并将参数t转发给process函数。通过使用std::forward(t),可以将原始参数的值类别(左值或右值)传递给process函数,从而调用合适的重载函数。

通过使用完美转发,可以更好地处理函数参数的转发,避免不必要的拷贝,提高代码的性能和效率。请注意,完美转发需要注意避免悬垂引用和引用折叠等问题,在实际使用中需要谨慎处理。

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

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

相关文章

万界星空科技电子装配行业MES解决方案

电子电器装配属于劳动密集型、科技含量较高的行业&#xff0c;产品零部件种类繁多&#xff0c;生产组装困难&#xff0c;生产过程存在盲点&#xff0c;同时也决定了生产流水线多且对自动化水平要求较高。 万界星空科技提供的电子行业MES解决方案&#xff0c;提供从仓储管理、生…

pytorch——房价预测

1、首先对数据进行读取和预处理 2、读取数据后&#xff0c;对x数据进行标准化处理&#xff0c;以便于后续训练的稳定性&#xff0c;并转换为tensor格式 3、接下来设置训练参数和模型 这里采用回归模型&#xff0c;既yx*weight1bias1&#xff0c;设置的学习率为0.0006&#x…

perl脚本中使用eval函数执行可能有异常的操作

perl脚本中有时候执行的操作可能会引发异常&#xff0c;为了直观的说明&#xff0c;这里举一个json反序列化的例子&#xff0c;脚本如下&#xff1a; #! /usr/bin/perl use v5.14; use JSON; use Data::Dumper;# 读取json字符串数据 my $json_str join(, <DATA>); # 反…

《地理信息系统原理》笔记/期末复习资料(12. 地理信息工程)

目录 12. 地理信息工程 12.1. 地理信息系统工程的概念 12.2. 地理信息系统工程建设过程 12.2.1. 应用型地理信息系统设计步骤和方法 12.2.2. 需求分析 12.2.3. 系统设计 12.2.4. 系统开发与实施 12.2.5. 系统的评价和维护 12.3. GIS标准 12.4. 习题 12. 地理信息工程…

CNN、LeNet、AlexNet基于MNIST数据集进行训练和测试,并可视化对比结果

完成内容&#xff1a; 构建CNN并基于MNIST数据集进行训练和测试构建LeNet并基于MNIST数据集进行训练和测试构建AlexNet并基于MNIST数据集进行训练和测试对比了不同网络在MNIST数据集上训练的效果 准备工作 import torch import torch.nn as nn import torch.optim as optim …

高通SDX12:nand flash适配

一、SBL阶段 代码流程如下: boot_images\core\storage\flash\src\dal\flash_nand_init.c nand_probe ->nand_intialize_primary_hal_device ->>nand_get_device_list_supportedboot_images\core\storage\flash\src\dal\flash_nand_config.c ->>>flash_n…

ue4 解决角度万向锁的问题 蓝图节点

问题&#xff1a;当角度值从359-1变化的时候&#xff0c;数值会经历358、357… 解决方法&#xff1a;勾上Shortest Path&#xff0c;角度值的会从359-1

Kubernetes实战(十三)-使用kube-bench检测Kubernetes集群安全

1 概述 在当今云原生应用的开发中&#xff0c;Kubernetes已经成为标准&#xff0c;然而&#xff0c;随着其使用的普及&#xff0c;也带来了安全问题的挑战。本文将介绍如何使用kube-bench工具来评估和增强Kubernetes集群的安全性。 2 CIS (Center for Internet Security)简介…

OneCode低代码引擎 V2.0源码结构详解

前言 OneCode今天&#xff08;12月10日&#xff09;正式更新了其V2.0版本。从OneCode的季度版本生命中&#xff0c;可以看到2.0版本还是一个重量级的版本&#xff0c;笔者在收到2.0更新后第一时间下拉了最新的代码。在参考了OneCode 的技术说明后&#xff0c;根据包结构来分析…

MySQL笔记-第16章_变量、流程控制与游标

视频链接&#xff1a;【MySQL数据库入门到大牛&#xff0c;mysql安装到优化&#xff0c;百科全书级&#xff0c;全网天花板】 文章目录 第16章_变量、流程控制与游标1. 变量1.1 系统变量1.1.1 系统变量分类1.1.2 查看系统变量 1.2 用户变量1.2.1 用户变量分类1.2.2 会话用户变量…

GoLang EASY 微服务游戏框架 01

1 Overview EASY 是一个go语言编写的框架&#xff0c;兼容性支持go版本1.19&#xff0c;go mod 方式构建管理。它是一个轻型&#xff0c;灵活&#xff0c;自定义适配强的微服务框架。 它支持多种网络协议TCP&#xff0c;websocket&#xff0c;UDP&#xff08;待完成&#xf…

数据可视化:解析跨行业普及之道

数据可视化作为一种强大的工具&#xff0c;在众多行业中得到了广泛的应用&#xff0c;其价值和优势不断被发掘和利用。今天就让我以这些年来可视化设计的经验&#xff0c;讨论一下数据可视化在各个行业中备受青睐的原因吧。 无论是商业、科学、医疗保健、金融还是教育领域&…

蚂蚁SEO的百度蜘蛛池有哪些优势

一、介绍 SEO是搜索引擎优化&#xff08;Search Engine Optimization&#xff09;的缩写&#xff0c;是一种通过优化网站结构、内容和链接等元素&#xff0c;提高网站在搜索引擎中的排名&#xff0c;从而增加网站流量和吸引更多潜在客户的方法。SEO已成为现代网站管理的重要策…

聚观早报 |一加12首销;华为智能手表释放科技温暖

【聚观365】12月12日消息 一加12首销 华为智能手表释放科技温暖 卡尔动力获地平线战略投资 英伟达希望在越南建立基地 努比亚Z60 Ultra影像规格揭晓 一加12首销 现在有最新消息&#xff0c;近日一加12该机已于昨日开售&#xff0c;售价4299元起。 外观方面&#xff0c;全…

IDC报告:国内游戏云市场,腾讯云用量规模位列第一

12月12日消息&#xff0c;IDC公布最新的《中国游戏云市场跟踪研究&#xff0c;2022H2》报告&#xff08;以下简称“《报告》”&#xff09;显示&#xff0c;腾讯云凭借全球化节点布局以及国际领先的游戏技术积累&#xff0c;在整体规模、云游戏流路数、CDN流量峰值带宽等多维度…

“未来医疗揭秘:机器学习+多组学数据,开启生物医学新纪元“

在当今的数字化时代&#xff0c;科技正在不断地改变着我们的生活&#xff0c;同时也为医疗领域带来了巨大的变革。随着机器学习的快速发展&#xff0c;以及多组学数据在生物医学中的应用&#xff0c;我们正开启一个全新的医疗纪元。这个纪元以精准诊断、个性化治疗和高效康复为…

Docker容器:Centos7搭建Docker镜像私服harbor

目录 1、安装docker 1.1、前置条件 1.2、查看当前操作系统的内核版本 1.3、卸载旧版本(可选) 1.4、安装需要的软件包 1.5、设置yum安装源 1.6、查看docker可用版本 1.7、安装docker 1.8、开启docker服务 1.9、安装阿里云镜像加速器 1.10、设置docker开机自启 2、安…

K8S(一)—安装部署

目录 安装部署前提以下的操作指导(在master)之前都是三台机器都需要执行 安装docker服务下面的操作仅在k8smaster执行 安装部署 前提 以下的操作指导(在master)之前都是三台机器都需要执行 关闭防火墙 [rootk8smaster ~]# vim /etc/selinux/config [rootk8smaster ~]# swa…

指针浅谈(三)

在指针浅谈(二)http://t.csdnimg.cn/SKAkD中我们讲到了const修饰指针、指针运算、野指针、assert断言和传址调用的内容&#xff0c;今天我们继续学习有关数组名、指针访问数组、一维数组传参的本质相关的内容&#xff0c;内容比较深入&#xff0c;如果觉得哪里讲解的不行&#…

Docker部署Nacos集群并用nginx反向代理负载均衡

首先找到Nacos官网给的Github仓库&#xff0c;里面有docker compose可以快速启动Nacos集群。 文章目录 一. 脚本概况二. 自定义修改1. example/cluster-hostname.yaml2. example/.env3. env/mysql.env4. env/nacos-hostname.env 三、运行四、nginx反向代理&#xff0c;负载均衡…