C++中如何利用右值引用和移动构造函数实现资源高效转移?

2026-04-12 08:231阅读0评论SEO教程
  • 内容介绍
  • 相关推荐

本文共计1816个文字,预计阅读时间需要8分钟。

C++中如何利用右值引用和移动构造函数实现资源高效转移?

目录一:背景二:右值引用应用

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

2.右值引用是什么样子的子

3.右值引用如何减少对象的创建

1. 简要思路 2. 一个简单的例子 3. 性能优化方案三:总结一:背景二:右值引用应用

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

2.右值引用是什么样子的子

3.右值引用如何减少对象的创建

1. 简要思路 2. 一个简单的例子 3. 性能优化方案三:总结

目录
  • 一: 背景
  • 二: 右值引用
    • 1. 它到底解决了什么问题
    • 2. 右值引用是个什么样子
  • 三: 右值引用如何减少对象的创建
    • 1. 简要思路
    • 2. 一个简单的例子
    • 3. 性能优化方案
  • 四: 总结

    一: 背景

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

    二: 右值引用

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

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

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

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

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

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

    1.10,11,(i+j)

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

    2.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. 简要思路

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

    C++中如何利用右值引用和移动构造函数实现资源高效转移?

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

    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++中右值引用和移动构造函数的使用的文章就介绍到这了,更多相关C++右值引用 移动构造函数内容请搜索自由互联以前的文章或继续浏览下面的相关文章希望大家以后多多支持自由互联!

    本文共计1816个文字,预计阅读时间需要8分钟。

    C++中如何利用右值引用和移动构造函数实现资源高效转移?

    目录一:背景二:右值引用应用

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

    2.右值引用是什么样子的子

    3.右值引用如何减少对象的创建

    1. 简要思路 2. 一个简单的例子 3. 性能优化方案三:总结一:背景二:右值引用应用

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

    2.右值引用是什么样子的子

    3.右值引用如何减少对象的创建

    1. 简要思路 2. 一个简单的例子 3. 性能优化方案三:总结

    目录
    • 一: 背景
    • 二: 右值引用
      • 1. 它到底解决了什么问题
      • 2. 右值引用是个什么样子
    • 三: 右值引用如何减少对象的创建
      • 1. 简要思路
      • 2. 一个简单的例子
      • 3. 性能优化方案
    • 四: 总结

      一: 背景

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

      二: 右值引用

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

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

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

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

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

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

      1.10,11,(i+j)

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

      2.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. 简要思路

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

      C++中如何利用右值引用和移动构造函数实现资源高效转移?

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

      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++中右值引用和移动构造函数的使用的文章就介绍到这了,更多相关C++右值引用 移动构造函数内容请搜索自由互联以前的文章或继续浏览下面的相关文章希望大家以后多多支持自由互联!