右值引用带来的效率提升(C++11)

news/2024/12/23 3:55:58/

在这里插入图片描述

文章目录

  • 一.左值引用和右值引用
  • 二.C++11区分左值和右值的语法设计意义--对象的移动构造和移动赋值
    • 场景分析1:
      • C++11之前
      • C++11之后
    • 场景分析2:
    • 函数std::move
    • 右值引用的广泛使用
  • 三.引用折叠

一.左值引用和右值引用

  • 左值:可以取到地址的对象(可以出现在赋值符号的左边),对左值的引用称为左值引用&.
  • 右值(将亡值):无法取到地址的对象(不能出现在赋值符号的左边),对右值的引用称为右值引用&&.
    • 常见的右值有:字面常量,表达式返回值,函数返回值(非const限定的引用)(通常是常量或临时变量)
void Testreference1()
{//以下的p、b、c、*p都是左值//可以取到地址的变量int* p = new int(0);int b = 1;const int c = 2;//以下几个是对上面左值的左值引用int* & rp = p;int & rb = b;const int & rc = c;int & pvalue = *p;
}void Testreference2()
{double x = 1.1, y = 2.2;//以下几个都是对右值的右值引用//常量int&& rr1 = 10;//表达式返回值double&& rr2 = (x + y);//非const修饰的函数返回值(fmin返回值为double)double&& rr3 = fmin(x, y);
}
  • const type&(被const限定的左值引用)既可以引用左值,也可以引用右值

二.C++11区分左值和右值的语法设计意义–对象的移动构造和移动赋值

  • C++11区分出左值和右值是为了能够让编译器识别出一些即将析构的类对象(将亡),当这些类对象作为引用形参拷贝给其他对象时,通过右值的类型识别,就可以调用相应重载版本拷贝构造或赋值运算符重载来实现对象堆区资源的交换转移(通过交换指向堆区的指针实现),从而避免了没必要的深拷贝

场景分析1:

C++11之前

  • C++11之前的string对象模拟(只含对象的构造,拷贝构造,赋值重载,析构函数):
namespace mystring
{class string{public://构造函数(深拷贝)string(const char* str = ""):_size(strlen(str)),_capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}//交换两个string对象(堆区资源交换)void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//拷贝构造(复用构造函数实现)(深拷贝)string(const string& s):_str(nullptr){cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;string tmp(s._str);swap(tmp);}//赋值重载(复用拷贝构造实现)(深拷贝)string& operator=(const string& s){cout << "string& operator=(string s) -- 赋值重载(深拷贝)" << endl;string tmp(s);swap(tmp);return *this;}//析构函数~string(){delete[] _str;_str = nullptr;}private:char* _str;size_t _size;size_t _capacity; };
}
  • 执行以下代码:
string to_string()
{string tem("对象测试");return tem;
}
//tem在函数调用完后就是一个即将析构的对象
int main()
{string s(to_string());return 0;
}

在这里插入图片描述

  • 类似的场景下,在C++11之前,要接收tem的数据就会进行对象的深拷贝:在这里插入图片描述
  • tem完成拷贝后就会调用析构函数释放其内存资源,这种情况下tem的堆区资源其实是被浪费掉的,如果不进行深拷贝,直接将tem对象与s对象中的指针进行交换,就可以实现堆区资源的转移,但是这种指针交换操作会让被拷贝的对象无法再使用,因此就需要确定被拷贝的对象是否为即将析构的对象,于是C++区分出了左值和右值来识别一些即将析构的对象

C++11之后

  • C++11之后的string对象模拟(只含对象的构造,拷贝构造,赋值重载,析构函数):
namespace mystring
{class string{public://构造函数string(const char* str = ""):_size(strlen(str)),_capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}//交换两个string对象(堆区资源交换)void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//拷贝构造(复用构造函数实现)string(const string& s):_str(nullptr){cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;string tmp(s._str);swap(tmp);}//赋值重载(复用拷贝构造实现)string& operator=(const string& s){cout << "string& operator=(string s) -- 赋值重载(深拷贝)" << endl;string tmp(s);swap(tmp);return *this;}//移动构造string(string && s):_str(nullptr), _size(0), _capacity(0){_str = new char[_capacity + 1];cout << "string(string&& s) -- 移动构造(资源转移)" << endl;swap(s);}//移动赋值string& operator=(string && s){cout << "string& operator=(string&& s) -- 移动赋值(资源转移)" << endl;swap(s);return *this;}//析构函数~string(){delete[] _str;_str = nullptr;}private:char* _str;size_t _size;size_t _capacity; };
}
  • C++11之后类对象新增了两个默认成员函数:移动赋值移动构造函数(本质上就是构造函数赋值运算符重载函数形参为右值引用类型的重载版本)
  • 当类对象直接申请了堆区内存,移动赋值和移动构造函数就需要开发者自行将其实现用于对象拷贝的场景,实现方式一般就是交换两个对象指向堆区内存的指针.
  • 执行同样的代码:
string to_string()
{string tem("对象测试");return tem;
}
//tem在函数调用完后就是一个即将析构的对象
int main()
{//函数返回值被识别成右值,调用移动构造交换堆区资源,避免了深拷贝string s(to_string());return 0;
}

在这里插入图片描述

  • tem被识别成了右值(将亡值),s的创建调用了形参为右值引用的构造函数重载版本(也就是移动构造),进行了s对象和tem对象的堆区资源交换,避免了深拷贝在这里插入图片描述

场景分析2:

  • 使用前述的C++11之前的string对象C++11之后的string对象分别执行以下代码:
string to_string()
{string tem("对象测试");return tem;
}
//tem在函数调用完后就是一个即将析构的对象
int main()
{string s;s = to_string();return 0;
}

在这里插入图片描述

  • 上面类似的场景中,移动构造移动赋值避免了两次深拷贝

函数std::move

  • 当一个左值对象需要被拷贝并且拷贝完后不再使用,它作为对象拷贝函数的引用形参时,可以使用move函数将其强制识别为右值,比如:
int main()
{//测试构造函数string s1("对象测试");//s1是左值对象,调用拷贝构造string s2(s1);//move强制让编译器将s1识别成右值,调用移动构造交换堆区资源string s3(std::move(s1));cout << endl;//测试赋值重载函数//函数返回值被识别成右值,调用移动赋值交换堆区资源,避免了深拷贝string s4("对象测试");//s4是左值对象,调用赋值重载s2 = s4;//move强制让编译器将s1识别成右值,调用移动赋值,交换堆区资源s3 = std::move(s1);
}

在这里插入图片描述

右值引用的广泛使用

  • C++11之后,STL标准模板库中的所有数据结构对象中,所有可能涉及对象深拷贝的成员接口(包括对象的构造函数,赋值重载函数以及增删查改接口)都增加了形参为对象右值引用的重载版本,从而有效地减少了STL在使用的过程中对象深拷贝的次数.类对象移动构造和移动赋值的设计模式,一定程度上提高了C++代码的时间效率

三.引用折叠

  • 引用折叠的概念:作为类型模板参数的引用可以起到引用折叠的作用,既可以接收左值引用也可以接收右值引用
template<typename T>
void PerfectForward(T&& t)
{
}
  • 代码段中的函数形参t既可以接收左值引用也可以接收右值引用,但是当t接收到右值引用时,会将其转化为左值引用(底层机制是在内存中开一段空间存储对象),我们希望能够在引用传递的过程中保持它的左值或者右值的属性,这时就需要std::forward函数来辅助引用参数传递(称为完美转发):在这里插入图片描述
int main()
{string s1("对象测试");list<string> List;List.push_back(std::move(s1));
}
  • 上面的STL的使用场景中就发生了引用的完美转发,list的push_back接口的内部代码结构:
template<class T>
struct ListNode
{ListNode* _next = nullptr;ListNode* _prev = nullptr;T _data;
};
template<class T>
class list
{typedef ListNode<T> Node;
public://复用Insert实现void push_back(T&& x)//引用折叠{//引用完美转发Insert(_head, std::forward<T>(x));}//链表结点插入接口void Insert(Node* pos, T&& x)//引用折叠{Node* prev = pos->_prev;Node* newnode = new Node;//引用完美转发newnode->_data = std::forward<T>(x);prev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}
private:Node* _head;
};

在这里插入图片描述

  • 最终s1的右值引用传递到了string对象的移动赋值函数中,避免了s1对象的深拷贝
    在这里插入图片描述

http://www.ppmy.cn/news/1001874.html

相关文章

小研究 - 领域驱动设计DDD在IT企业内部网站开发中的运用(一)

在企业内部网站的建设过程中&#xff0c;网站后端最初采用传统的表模式的开发方式。这种方式极易导致站点的核心业务逻辑和业务规则分布在架构的各个层和对象中&#xff0c;这使得系统业务逻辑的复用性不高。为了解决这个问题&#xff0c;作者在后期的开发过程中引入了领域驱动…

uniapp h5支付宝支付后端返回Form表单,前端如何处理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言1.调取接口拿到后端返回的form表单 前言 uniapp h5 支付宝支付&#xff0c;后端返回一串form表单&#xff0c;前端如何拿到支付串并且调用支付 1.调取接口拿到…

c++--简单多状态动态规划问题

PS:以下代码均为C实现 1.按摩师 力扣 一个有名的按摩师会收到源源不断的预约请求&#xff0c;每个预约都可以选择接或不接。在每次预约服务之间要有休息时间&#xff0c;因此她不能接受相邻的预约。给定一个预约请求序列&#xff0c;替按摩师找到最优的预约集合&#xff08;总…

【BASH】回顾与知识点梳理(五)

【BASH】回顾与知识点梳理 五 五. 数据流重导向5.1 什么是数据流重导向standard output 与 standard error output/dev/null 垃圾桶黑洞装置与特殊写法standard input &#xff1a; < 与 << 5.2 命令执行的判断依据&#xff1a; ; , &&, ||cmd ; cmd (不考虑指…

spring boot 服务健康检测返回OUT_OF_SERVICE,导致服务无法成功注册到consul

一、背景 健康检测接口返回OUT_OF_SERVICE curl -X GET http://192.168.5.53:8085/mgm/health{"status":"OUT_OF_SERVICE"}从日志启动看&#xff0c;没有任何报错信息&#xff1b;而且jvm进程也启动成功。 关键的一点信息是&#xff0c;服务的swagger地…

stm32常见数据类型

stm32的数据类型的字节长度 s8 占用1个byte&#xff0c;数据范围 -2^7 到 (2^7-1) s16 占用2个byte&#xff0c;数据范围 -2^15 到 (2^15-1) s32 占用 4个byte&#xff0c;数据范围 -2^31 到 (231-1)231 2147483647 int64_t占用8个byte&#xff0c;数据范围 -2^63 到 (2^63-1)…

TCP三次握手与四次断开

TCP三次握手机制 三次握手是指建立一个TCP连接时&#xff0c;需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。 1、客户端发送建立TCP连接的请求报文&#xff0c;其…

2023 8-2 ~ 8-3

2181 合并0之间的节点 思路挺简单 就是注意一下指针不要制空 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNod…