右值引用带来的效率提升(C++11)
文章目录
- 一.左值引用和右值引用
- 二.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对象的深拷贝
右值引用带来的效率提升(C++11)相关推荐
- 可变参数模板、右值引用带来的移动语义完美转发、lambda表达式的理解
可变参数模板 可变参数模板对参数进行了高度泛化,可以表示任意数目.任意类型的参数: 语法为:在class或者typename后面带上省略号. Template<class ... T> v ...
- C++11中的右值引用
http://www.cnblogs.com/yanqi0124/p/4723698.html 在C++98中有左值和右值的概念,不过这两个概念对于很多程序员并不关心,因为不知道这两个概念照样可以写出 ...
- C++面试 左值、右值、左值引用、右值引用
1.左值和右值 左值(left-values),缩写:lvalues ,located value 可定位值,其含义是可以明确其存放地址的值,更确切说对其的使用是基于地址 右值(right-valu ...
- C++11标准之右值引用(ravalue reference)
C++11标准之右值引用(ravalue reference) 1.右值引用引入的背景 临时对象的产生和拷贝所带来的效率折损,一直是C++所为人诟病的问题.但是C++标准允许编译器对于临时对象的产生具 ...
- C++ 右值引用 | 左值、右值、move、移动语义、引用限定符
文章目录 C++11为什么引入右值? 区分左值引用.右值引用 move 移动语义 移动构造函数 移动赋值运算符 合成的移动操作 小结 引用限定符 规定this是左值or右值 引用限定符与重载 C++1 ...
- C++ 11 中的右值引用
C++ 11 中的右值引用 右值引用的功能 首先,我并不介绍什么是右值引用,而是以一个例子里来介绍一下右值引用的功能: #include <iostream> #include & ...
- 《C++0x漫谈》系列之:右值引用
右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一,这点从该特性的提案在C++ - State of the Evolution列表上高居榜首也可以看得出来.从实践角 ...
- C++11右值引用和std::move语句实例解析
关键字:C++11,右值引用,rvalue,std::move,VS 2015 OS:Windows 10 右值引用(及其支持的Move语意和完美转发)是C++0x加入的最重大语言特性之一.从实践角度 ...
- C++11新特性(一)右值引用
@ 一.C++11简介 在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称.不过由于TC1主要是对C+ ...
最新文章
- 轻量级分布式 RPC 框架
- 解决vnc灰屏,黑屏,鼠标大黑叉情况
- hadoop1.2.1伪分布模式配置
- 引入OAuth2的主要目的
- 新人报道,写的东西还请大神们多指导!也希望能让和我一样的同事少走弯路。...
- vivo Y66的usb调试模式在哪里,打开vivo Y66usb调试模式的流程
- 抵御风险——漫谈运维核心价值和方法论
- 添加同名工具后台验证后不跳转且保留用户输入的数值
- 关于传说中的AMD5600G 发热少,功耗低
- 【收藏】40 个学术网站,满足科研文献需求!
- 留学生Essay写作没思路的解决方案
- canva怎么组合_教你使用Canvas合成图片
- Atcoder ABC162 D - RGB Triplets
- 硬件设备使用网线连接PC并访问外网
- Kubernetes监控:Dashbaord 2.0.0部署方式
- React基础(貳)———组件
- word中参考文献编号添加及更新方法
- 三维重建(5)之三角测量计算双目相机坐标系下三维坐标
- python猜数字游戏、在程序中预设一个_python 语法基础练习题
- 分钱、用人和交替,看《大明王朝1566》
热门文章
- “九韶杯”河科院程序设计协会第一届程序设计竞赛题解
- Arctic Network题解+(最小生成树二次理解 )
- 台式计算机可以看视频吗,台式电脑看视频突然关机怎么解决
- 【100%通过率】华为OD机试真题 JS 实现【数字加减游戏】【2023 Q1 | 100分】
- 微信公众号开发 - 引导用户关注、一键关注
- 51单片机入门 - 基础知识汇总
- 计算机二级c语言复习计划,寒假复习计划——C语言篇
- StrongSwan虚拟测试环境搭建
- 景观设计主题命名_景观命名汇总总结
- .md文件以及markdown语法书写md文档