一: 背景

最近在看 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. vue3 vite 系统标题 系统名称统一配置

    想要统一配置系统名称 或者其他的,需要在vue3中使用 vite 的环境变量 vite 的环境变量 需要创建两个文件(和 vite.config.js 文件同一目录) .env.development ...

  2. 使用本地自签名证书为 React 项目启用 https 支持

    简介 现在是大前端的时代,我们在本地开发 React 项目非常方便.这不是本文的重点,今天要分享一个话题是,如何为这些本地的项目,添加 https 的支持.为什么要考虑这个问题呢?主要有几个原因 如果 ...

  3. .NET混合开发解决方案14 WebView2的基本身份验证

    系列目录     [已更新最新开发文章,点击查看详细] WebView2控件应用详解系列博客 .NET桌面程序集成Web网页开发的十种解决方案 .NET混合开发解决方案1 WebView2简介 .NE ...

  4. Event Loop 是什么?

    Event Loop 是什么? 本文写于 2020 年 12 月 6 日 广义上来说 Event Loop 并不是 JavaScript 独有的概念,他是一个计算机的通用概念. 狭义上来说,只有 No ...

  5. linux篇--mysql数据库备份并删除前一分钟的数据

    linux 中mysql数据库定时备份并删除前一分钟的所有数据 #!/bin/bash #mysqldump -uroot -ppassword01! imaginebase > /home/b ...

  6. mysql外键与表查询

    目录 自增特性 外键 外键关系 外键创建 外键的约束效果 级联更新级联删除 多对多关系 一对一关系 表查询关键字 select与from where筛选 group by分组 练习 关系练习 查询练习 ...

  7. 每天一个 HTTP 状态码 102

    102 Processing 102 Processing 是用于 WebDAV协议 请求的状态码. 这个状态码表示服务器已经收到了客户端的请求,正在处理,但暂时还没有可接触的响应.可以用于防止客户端 ...

  8. 关于『Markdown』:第一弹

    关于『Markdown』:第一弹 建议缩放90%食用 声明: 在我之前已有数位大佬发布 "Markdown" 的语法知识点, 在此, 仅整理归类以及补缺, 方便阅读. 感谢 C20 ...

  9. CentOS切换用户命令su or su+username

    1.打开终端,提示符为"$",表明该用户为普通用户,此时,直接输su,回车,输入root密码,回车,就可以切换到root用户下,此时的提示符变为"#". 注意, ...

  10. element ui 自定义主题失败(primordials is not defined)

    卸载: 1.卸载cnpm npm uninstall cnpm -g 2.卸载vue-cli npm uninstall @vue/cli -g 3.卸载nodejs和删除文件 C:\Program ...