文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

聊聊 C++ 右值引用 和 移动构造函数

2024-12-01 18:51

关注

二、右值引用

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

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

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

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

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

int main()
{
int i = 10;
int j = 11;

int sum = i + j;
}

10,11,(i+j)

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

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++ 的值类型优先的基因决定的。

来源:一线码农聊技术内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯