一: 背景

最近在看 C++ 的右值引用和移动构造函数,感觉这东西一时半会还挺难理解的,可能是没踩过这方面的坑,所以没有那么大的深有体会,不管怎么说,这一篇我试着聊一下。

二: 右值引用

1. 它到底解决了什么问题?

在其他编程语言中,很少听到 右值引用 这个词,我个人感觉还是 C++ 这个 值类型 优先的语言基因决定的,我们都知道 值类型 作为方法参数或者返回值时会生成自身的副本,如果 值类型 很大,那一来一回生成若干个深复制的 临时对象 将会产生巨大的性能开销。

总结一句话:右值引用 就是尽可能的减少这中间 临时对象 个数,尤其是关联到 heap 上的对象,仅此而已。

2. 右值引用是个什么样子?

说到 右值引用 得先说什么是 右值,左值左值 一般都是带有内存地址的变量,而 右值 一般是立即数或者运算过程中的临时对象,这种对象不会有地址值,是不是很绕,我举个例子吧。


int main()
{
int i = 10;
int j = 11; int sum = i + j;
}
  1. 10,11,(i+j)

属于右值,因为它本身没有内存地址,除非把它们放入到栈中或者堆中。

  1. i,j,sum

属于左值,因为它们是线程栈上地址的标识符。

知道了 左右值 概念,接下来理解 左右值引用 就很简单了,既然是 引用,必然是多个变量指向同一个地址,对吧,修改下代码如下:


int main()
{
int i = 10;
int& k = i; //左值引用 int&& m = 10; //右值引用
}

接下来看下汇编代码:


33: int i = 10;
00FB182F mov dword ptr [ebp-0Ch],0Ah
34: int& k = i;
00FB182F mov dword ptr [ebp-0Ch],0Ah
00FB1836 lea eax,[ebp-0Ch]
00FB1839 mov dword ptr [ebp-18h],eax
36: int&& m = 10;
00FB183C mov dword ptr [ebp-30h],0Ah
00FB1843 lea eax,[ebp-30h]
00FB1846 mov dword ptr [ebp-24h],eax

从汇编代码看,它们是一模一样的,也就是说在汇编层面,其实并没有 右值引用左值引用 一说。

有了这些基础,我们来看下更复杂的 class 结构。

三: 右值引用如何减少对象的创建

1. 简要思路

其实仔细想一想,减少临时对象的创建,无非就是在运算过程中复用一些对象,不需要每次都走赋值构造函数来进行深复制,画个图就像下面这样。

明白了这个思路,接下来我们举一个例子说明。

2. 一个简单的例子

C++ 最烦的地方就是有太多的构造函数, 数不胜数,太尴尬了,这里我做一个简单的 + 操作例子。



#include <iostream>
#include <vector> using namespace std; class StringBuidler {
public:
char* str;
int length;
public:
StringBuidler() {}
StringBuidler(int len, char c) {
this->str = new char[len];
this->str[0] = c;
this->length = len;
} StringBuidler(const StringBuidler& s) { printf("StringBuidler:深复制 \n");
this->length = s.length;
this->str = new char[s.length]; for (size_t i = 0; i < length; i++)
{
this->str[i] = s.str[i];
}
} StringBuidler operator+(const StringBuidler& p) { StringBuidler tmp; tmp.length = this->length + p.length;
tmp.str = new char[tmp.length]; int index = 0; for (size_t i = 0; i < this->length; i++)
{
tmp.str[index++] = this->str[i];
}
for (size_t i = 0; i < p.length; i++)
{
tmp.str[index++] = p.str[i];
} return tmp;
}
}; int main()
{
StringBuidler s1(10, 'a');
StringBuidler s2(5, 'b'); StringBuidler s3 = s1 + s2; printf("s3.length=%d, s1.length=%d, s2.length=%d \n", s3.length, s1.length, s2.length);
}

从这个例子中可以看到,s1+s2 操作中出现了一次 深copy,具体代码出现在 return 处,汇编代码如下:

因为是深复制,所以会再次生成一个 new char[] ,如果 new char[] 很大,那将会是不必要的性能开销,能不能像我画的图一样,将 s3 中的 str 指针直接指向 tmp 所持有的 heap 上的 char[] 数组来达到复用目的呢? 肯定是可以的。

3. 性能优化方案

这里需要用 右值引用 + 移动构造函数s3.str 指向 tmp.str,从而避免复制构造函数,在 StringBuilder 类中加一个方法如下:


StringBuidler(StringBuidler&& s) {
this->str = s.str;
this->length = s.length; s.str = nullptr;
}

然后把程序跑起来,截图如下:

可以看到,深复制已经没有了,这个过程会在 return 处被调用,编译器会判断如果是右值的话,自动走 移动构造函数,没有这个函数就会走 赋值构造函数

四: 总结

总之 右值引用 可以让你尽可能的复用一些中间对象,达到一个性能上的提升,其实对 C# 程序员来说,这么简单的引用赋值,C++ 搞出了这么多概念,真的很难理解,可能还是那句话,这是 C++ 的值类型优先的基因决定的。

聊聊 C++ 右值引用 和 移动构造函数的更多相关文章

  1. C++11新特性:右值引用和转移构造函数

    问题背景 #include <iostream> using namespace std; vector<int> doubleValues (const vector< ...

  2. C++11新特性,对象移动,右值引用,移动构造函数

    C++11新标准中的一个最主要的特性就是移动而非拷贝对象的能力.接下来简要介绍一下相关概念. 右值引用 所谓右值引用就是必须绑定到右值的引用.通过 && 而不是 & 来获得右值 ...

  3. c++右值引用和转移构造函数

    int &&i = ; //i绑定到了右值1 int b = ; cout << i << endl; //输出1 i = b; cout << i ...

  4. [转载] C++11中的右值引用

    C++11中的右值引用 May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移动语义std::move() 右值引用和右值的关系 完美转发 引用折叠推导 ...

  5. 翻译「C++ Rvalue References Explained」C++右值引用详解 Part3:右值引用

    本文为第三部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/4220233.html. 右值引用 如果x是任意类型,那么x&&则被称作一个 ...

  6. C++11中的右值引用

    原文出处:http://kuring.me/post/cpp11_right_reference May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移 ...

  7. (原创)C++11改进我们的程序之右值引用

    本次主要讲c++11中的右值引用,后面还会讲到右值引用如何结合std::move优化我们的程序. c++11增加了一个新的类型,称作右值引用(R-value reference),标记为T & ...

  8. 详解C++右值引用

    C++0x标准出来很长时间了,引入了很多牛逼的特性[1].其中一个便是右值引用,Thomas Becker的文章[2]很全面的介绍了这个特性,读后有如醍醐灌顶,翻译在此以便深入理解. 目录 概述 mo ...

  9. C++11新特性之0——移动语义、移动构造函数和右值引用

    C++引用现在分为左值引用(能取得其地址)和 右值引用(不能取得其地址).其实很好理解,左值引用中的左值一般指的是出现在等号左边的值(带名称的变量,带*号的指针等一类的数据),程序能对这样的左值进行引 ...

随机推荐

  1. Centos 7.4_64位系统安装指南

    小土豆Linux学习随笔 -- 清听凌雪慕忆 目录 1. 范围 1.1标识 1.2 文档概述 2. 安装环境 3. 安装步骤 4. 注意事项 1. 范围 1.1标识 CentOS 7.4 64位系统安 ...

  2. js动态生成vue组件

    代码奉上 install (Vue, options) { Vue.prototype.$message = function (message){ let Constructor = Vue.ext ...

  3. 【Python数据分析案例】python数据分析老番茄B站数据(pandas常用基础数据分析代码)

    一.爬取老番茄B站数据 前几天开发了一个python爬虫脚本,成功爬取了B站李子柒的视频数据,共142个视频,17个字段,含: 视频标题,视频地址,视频上传时间,视频时长,是否合作视频,视频分区,弹幕 ...

  4. app嵌套页

    Wdatepicker

  5. '@JvmDefault' is only supported since JVM target 1.8. Recompile with '-jvm-ta

    Java 1.8加入了接口默认实现的方式. 那么在kotlin中如何使用呢? 可以在方法上加注解@JvmDefault 但直接加上后会编译会碰到一些错误提示.下面是解决方法. 问题 Logcat 提示 ...

  6. 现有教学数据库JX_DB,作业

    现有教学数据库JX_DB,数据库有以下三个基本表: 学生表student,它由学号sno.姓名sname.性别sex.出生日期Bdate.所在系dept五个属性构成.其中,学号不能为空,值是唯一的: ...

  7. FinClip 前端之 VUE 核心原理总结

    小程序框架有很多,都是支持前端JavaScript语言的,也是支持 vue.js 框架的.FinClip 小程序是兼容各家平台的.所以在学习了框架使用之后的进阶就要熟悉框架的底层原理. 1.数据响应式 ...

  8. HCNP Routing&Switching之链路聚合

    前文我们了解了MSTP相关话题,回顾清参考https://www.cnblogs.com/qiuhom-1874/p/16268682.html:今天我们来聊一聊链路聚合相关话题: 链路聚合是链路高可 ...

  9. JavaScript中if语句优化和部分语法糖小技巧推荐

    前言 在前端日常开发过程中,if else判断语句使用的次数应该是比较频繁的了,一些较为复杂的场景,可能会用到很多判断,在某个代码块使用很多if else时,代码会显得较为冗余,阅读起来不够清晰. 除 ...

  10. 120_PowerBI堆积瀑布图_R脚本Visual

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.效果 二.data 三.添加字段 注意红色框标注地方 四.code # 下面用于创建数据帧并删除重复行的代码始终执行, ...