*以下内容为本人的学习笔记,如需要转载,请声明原文链接微信公众号「ENG八戒」https://mp.weixin.qq.com/s/Xd_FwT8E8Yx9Vnb64h6C8w

带给现代 C++ 性能飞跃的特性很多,今天一边聊技术,一边送福利!


过去写 C/C++ 代码,大家对数据做传递时,都习惯先拷贝再赋值。比如,把数据从 t1 复制到 t2,复制完成后 t2 和 t1 的状态是一致的,t1 状态没变。这里的状态指的是对象内部的非静态成员数据集合。

在程序运行过程中,复制过程既要分配空间又要拷贝内容,对于空间和时间都是种损耗。复制操作,无疑是一门很大的开销,何况经常触发资源复制的时候。

来看看普通的函数返回值到底有哪些开销,

std::string getString()
{
std::string s;
// ... return s;
} int main()
{
std::string str = getString();
// ...
}

假设你的编译器还不支持 C++ 11,那么,在 main() 函数里调用 getString() 时,需要在调用栈里分配临时对象用于复制 getString() 的返回值 s,复制完成调用 s 的析构函数释放对象。然后,再调用 std::string 类的复制赋值运算符函数将临时对象复制到 str,同时调用临时对象的析构函数执行释放。

那么,有没有技巧可以实现上面示例代码同样的效果,同时避免复制?

有的,就是接下来重点介绍的移动(和中国移动无关)。

相对于复制,移动无须重新分配空间和拷贝内容,只需把源对象的数据重新分配给目标对象即可。移动后目标对象状态与移动前的源对象状态一致,但是移动后源对象状态被清空。

实际上,大部份的情况下,数据仅仅需要移动即可,拷贝复制显得多余。就像,你从图书馆借书,把自己手机的 SIM 卡拔出来再插到其它手机上,去商店买东西你的钱从口袋移动到收银柜等等。

那么,是不是可以对所有的数据都执行移动?

答案是否定的。在现代 C++ 中,只有右值可以被移动。

左右值概念

在 C++ 11 之前,左右值的划分比较简单,只有左值和右值两种。

但是从 C++ 11 开始,重新把值类别划分成了五种,左值(lvalue, left value),将亡值(xvalue, expiring value),纯右值(prvalue, pure right value),泛左值(glvalue, generalized left value),右值(rvalue, right value)。不过后边的两种 glvalue 和 rvalue 是基于前面的三种组合而成。从集合概念来看,glvalue 包含 lvalue 和 xvalue,rvalue 包含 xvalue 和 prvalue。

左右值划分的依据是:具名和可被移动。

具名,简单点理解就是寻址。可被移动,允许对量的内部资源移动到其它位置,并且保持量自身是有效的,但是状态不确定。

  • lvalue:具名且不可移动
  • xvalue:具名且可移动
  • prvalue:不具名且可移动

那么,可以看到泛左值(glvalue)其实就是具名的量,右值就是可移动的量。

以往在往函数传参的时候,经常有用到值引用的模式,形式如下:

function(T& obj)

T 是类型,obj 是参数。

到了现代 C++,原来的值引用就变成了左值引用,另外还出现了右值引用,形式如下:

function(T&& obj)

那么 C++ 11 是怎样实现移动操作的呢?

实现移动操作

移动操作依赖于类内部特殊成员函数的执行,但前提是该对象是可移动的。如果恰好对象是左值(lvalue)呢?

C++ 11 的标准库就提供了 std::move() 实现左右值转换操作。std::move() 用于将表达式从 lvalue(左值) 转换成 xvalue(将亡值),但不会对数值执行移动。当然,使用强制类型转换也是可以达到同样目的。

std::move(obj); // 等价于 static_cast<T&&>(obj);

在 stack overflow 上看到对 std::move() 的一段描述,与其说它是一个函数,不如说,它是编译器对表达式值评估的方式转换器。

以往惯常使用 C++ 类定义时,我们都知道有这么几个特殊的成员函数:

  • 默认构造函数(default constructor)
  • 复制构造函数(copy constructor)
  • 复制赋值运算符函数(copy assignment operator)
  • 析构函数(destructor)

来看看一个简单的例子:

class MB // MemoryBlock
{
public:
// 为下面代码演示简单起见
// 在 public 定义成员属性
size_t size;
char *buf; // 默认构造函数
explicit MB(int sz = 1024)
: size(sz), buf(new char[sz]) {}
// 析构函数
~MB() {
if (buf != nullptr) {
delete[] buf;
}
}
// 复制构造函数
MB(const MB& obj)
: size(obj.size),
buf(new char[obj.size]) {
memcpy(buf, obj.buf, size);
}
// 复制赋值运算符函数
MB& operator=(const MB& obj) {
if (this != &obj) {
if (buf != nullptr) {
delete[] buf;
}
size = obj.size;
buf = new char[size];
memcpy(buf, obj.buf, size);
}
return *this;
}
}

为了支持移动操作,从 C++ 11 开始,类定义里新增了两个特殊成员函数:

  • 移动构造函数(move constructor)
  • 移动赋值运算符函数(move assignment operator)

移动构造函数

在构造新对象时,如果传入的参数是右值引用对象,就会调用移动构造函数创建对象。如果没有自定义移动构造函数,那么编译器就会自动生成,默认实现是遍历调用成员属性的移动构造函数,并移动右值对象的成员属性数据到新对象。

定义一般声明形式如下:

T::T(C&& other);

基于上面的简单例子:

class MB // MemoryBlock
{
public:
// ... // 移动构造函数
MB(MB&& obj)
: size(0), buf(nullptr) {
// 移动源对象数据到新对象
size = obj.size;
buf = obj.buf;
// 清空源对象状态
// 避免析构函数多次释放资源
obj.size = 0;
obj.buf = nullptr;
}
}

可见,移动构造函数的执行过程,仅仅是简单赋值的过程,不涉及拷贝资源的耗时操作,自然执行效率大大提高。

移动赋值运算符函数

在调用赋值运算符时,如果右边传入的参数是右值引用对象,就会调用移动赋值运算符函数。同样,如果没有自定义移动赋值运算符函数,那么编译器也会自动生成,默认实现是遍历调用成员属性的移动赋值运算符函数并移动成员属性的数据到左边参数对象。

一般声明形式如下:

T& T::operator=(C&& other);

基于上面的简单例子:

class MB // MemoryBlock
{
public:
// ... // 移动赋值运算符函数
MB& MB::operator=(MB&& obj) {
if (this != &obj) {
if (buf != nullptr) {
delete[] buf;
}
// 移动源对象数据到新对象
size = obj.size;
buf = obj.buf;
// 清空源对象状态
// 避免析构函数多次释放资源
obj.size = 0;
obj.buf = nullptr;
}
return *this;
}
}

移动赋值运算符函数的执行过程,同样仅仅是简单赋值的过程,执行效率明显远超复制操作。

总结

回顾文首的示例代码,由于 C++ 11 加入了返回值优化 RVO(Return Value Optimization) 的特性,所以代码无需变更即可获得效率提升。对于部分编译器而言,比如 IBM Compiler、Visual C++ 2010 等,已经提前具备返回值优化的支持。

对于 RVO 的内容,暂不展开讨论,有兴趣的同学可以关注公众号【ENG八戒】了解后续更新,关注后甚至可以参与赠书活动!


现代 C++ 性能飞跃之:移动语义的更多相关文章

  1. PHP从PHP5.0到PHP7.1的性能全评测

    本文是最初是来自国外的这篇:PHP Performance Evolution 2016, 感谢高可用架构公众号翻译成了中文版, 此处是转载的高可用架构翻译后的文章从PHP 5到PHP 7性能全评测( ...

  2. PHP的性能演进(从PHP5.0到PHP7.1的性能全评测)

    本文是最初是来自国外的这篇:PHP Performance Evolution 2016, 感谢高可用架构公众号翻译成了中文版, 此处是转载的高可用架构翻译后的文章从PHP 5到PHP 7性能全评测( ...

  3. 如何提高Java并行程序性能??

    在Java程序中,多线程几乎已经无处不在.与单线程相比,多线程程序的设计和实现略微困难,但通过多线程,我们却可以获得多核CPU带来的性能飞跃,从这个角度说,多线程是一种值得尝试的技术.那么如何写出高效 ...

  4. 从PHP5.0到PHP7.1的性能全评测

    本文是最初是来自国外的这篇:PHP Performance Evolution 2016, 感谢高可用架构公众号翻译成了中文版, 此处是转载的高可用架构翻译后的文章从PHP 5到PHP 7性能全评测( ...

  5. C++11中右值引用和移动语义

    目录 左值.右值.左值引用.右值引用 右值引用和统一引用 使用右值引用,避免深拷贝,优化程序性能 std::move()移动语义 std::forward()完美转发 容器中的emplace_back ...

  6. java之高并发与多线程

    进程和线程的区别和联系 从资源占用,切换效率,通信方式等方面解答 线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元:而把传统的进程称为重型进程(H ...

  7. 【风雪之隅】写在PHP7发布之际一些话 2015-12-02

    做开源也有4,5年的时间了,从最初的 Yaf,到今天的 PHP7,我参与的项目越来越多,使用我代码的用户也越来越多,明天就要发布的PHP7,绝对是我从事开源以来的一个最重要里程碑,我应该纪念一下今天, ...

  8. C#下内存管理--垃圾收集

    章节安排 内存管理简介 垃圾回收机制 性能问题 C#下非托管资源的处理 要强调的几点 References 内存管理简介 对于任何一种编程语言,内存管理都是不得不提很重要的一块内容,但可惜的是目前为止 ...

  9. 2016值得关注的语言平台、JS框架

    语言和平台 Python 3.5 在今年发布了,带来了很多新特性 比如 Asyncio,,为你带来了类似 node.js 的事件机制,还有type hints. 鉴于Python 3 终于真正地火起来 ...

  10. 强大核心功能矩阵,详解腾讯云负载均衡CLB高可靠高性能背后架构

    1 前言 腾讯云负载均衡(Cloud LoadBalancer),简称CLB, 负载均衡通过设置虚拟服务地址(VIP)将来自客户端的请求按照指定方式分发到其关联的多台后端云服务器,服务器将请求的响应返 ...

随机推荐

  1. 如何用Python对股票数据进行LSTM神经网络和XGboost机器学习预测分析(附源码和详细步骤),学会的小伙伴们说不定就成为炒股专家一夜暴富了

    前言 最近调研了一下我做的项目受欢迎程度,大数据分析方向竟然排第一,尤其是这两年受疫情影响,大家都非常担心自家公司裁员或倒闭,都想着有没有其他副业搞搞或者炒炒股.投资点理财产品,未雨绸缪,所以不少小伙 ...

  2. 剑指 offer 第 3 天

    第 3 天 字符串(简单) 剑指 Offer 05. 替换空格 请实现一个函数,把字符串 s 中的每个空格替换成"%20". 示例 1: 输入:s = "We are h ...

  3. Windows11快捷键大集合+手动给程序添加快捷键

    本文收集了170多个windows11上的快捷键,其中有少部分是windows11新添加的.大部分的win10快捷键也适用于win11.这些快捷键涵盖了系统设置.命令行程序执行.Snap布局切换.对话 ...

  4. 浅学git工具

    1.git工具介绍及使用 git工具直接安装: 直接运行exe文件进行安装,按默认的操作点击下一步就行了 校验: 在DOS命令行中输入:git  --version 如果能正常显示出对应的版本就是ok ...

  5. python线程之event事件

    from threading import Thread, Event import time event = Event() def light(): print('红灯亮着,所有车都要等待') t ...

  6. [架构]辨析: 高可用 | 集群 | 主从 | 负载均衡 | 反向代理 | 中间件 | 微服务 | 容器 | 云原生 | DevOps | ...

    词汇集 灾备 冷备份 双机热备份 异地容灾备份 云备份 灾难演练 磁盘阵列(RAID) 故障切换 心跳监测 高可用 集群 主从复制(Master-Slave) 多集群横向扩容(master-clust ...

  7. vulnhub靶场之DRIFTINGBLUES: 5

    准备: 攻击机:虚拟机kali.本机win10. 靶机:DriftingBlues: 5,下载地址:https://download.vulnhub.com/driftingblues/driftin ...

  8. ILLA Cloud: 调用 Hugging Face Inference Endpoints,开启大模型世界之门

    一个月前,我们 宣布了与 ILLA Cloud 与达成的合作,ILLA Cloud 正式支持集成 Hugging Face Hub 上的 AI 模型库和其他相关功能. 今天,我们为大家带来 ILLA ...

  9. MySQL MHA信息的收集【Filebeat+logstash+MySQL】

    一.项目背景 随着集团MHA集群的日渐增长,MHA管理平台话越来越迫切.而MHA平台的建设第一步就是将这些成百上千套的MHA集群信息收集起来,便于查询和管理. MHA主要信息如下: (1)基础配置信息 ...

  10. Java中「Future」接口详解

    目录 一.背景 二.Future接口 1.入门案例 2.Future接口 三.CompletableFuture类 1.基础说明 2.核心方法 2.1 实例方法 2.2 计算方法 2.3 结果获取方法 ...