C++雾中风景9:emplace_back与可变长模板
C++11的版本在vector容器添加了emplace_back方法,相对于原先的push_back方法能够在一定程度上提升vector容器的表现性能。所以我们从STL源码角度来切入,看看这两种方法有什么样的区别,新引进的方法又有什么可学习参考之处。
1.emplace_back的用法
emplace_back方法最大的改进就在与可以利用类本身的构造函数直接在内存之中构建对象,而不需要调用类的拷贝构造函数与移动构造函数。
举个栗子,假设如下定义了一个时间类time,该类同时定义了拷贝构造函数与移动构造函数:
class time { private: int hour; int minute; int second; public: time(int h, int m, int s) :hour(h), minute(m), second(s) { } time(const time& t) :hour(t.hour), minute(t.minute), second(t.second) { cout << "copy" << endl; } time(const time&& t) noexcept:hour(t.hour),minute(t.minute),second(t.second) { cout << "move" << endl; } };
在main方法之中执行下面的代码逻辑:
int main() { vector<time> tlist; time t(1, 2, 3); tlist.emplace_back(t); tlist.emplace_back(2, 3, 4); //直接调用了time的构造函数在vector的内存之中建立起新的对象 getchar(); }
执行结果:
copy move (这次拷贝构造函数的调用是因为vector本身的扩容,也就是移动之前的已经容纳的time对象)
由上述代码我们看到time对象可以直接利用emplace_back方法在vector上构造对象,通过这样的方式来减少不必要的内存操作。(省去了拷贝构造的环节)。同样的在main之中执行下面的代码逻辑:
int main() { vector<time> tlist; time t(1, 2, 3); tlist.emplace_back(move(t)); //调用move函数使time对象成为右值,可以利用移动构造函数来拷贝对象 tlist.emplace_back(2, 3, 4); //直接调用了time的构造函数在vector的内存之中建立起新的对象 getchar(); }
执行结果:
move move (这次拷贝构造函数的调用是因为vector本身的扩容,也就是移动之前的已经容纳的time对象)
通过这样的方式也减少不必要的内存操作。(省去了移动构造的环节)。所以这就是为什么在C++11之后提倡大家使用emplace_back来代替旧代码之中的push_back函数。如下面的代码所示,在push_back底层也是调用了emplace_back来实现对应的操作流程:
void push_back(const _Ty& _Val) { emplace_back(_Val); } void push_back(_Ty&& _Val) { emplace_back(_STD move(_Val)); }
2.emplace_back的实现
源码面前,了无秘密,接下来跟随笔者直接来看看emplace_back的源代码,来引出我们今天的主题:
public: template<class... _Valty> decltype(auto) emplace_back(_Valty&&... _Val) { // insert by perfectly forwarding into element at end, provide strong guarantee if (_Has_unused_capacity()) { _Emplace_back_with_unused_capacity(_STD forward<_Valty>(_Val)...); } else { // reallocate const size_type _Oldsize = size(); if (_Oldsize == max_size()) { _Xlength(); } const size_type _Newsize = _Oldsize + 1; const size_type _Newcapacity = _Calculate_growth(_Newsize); bool _Emplaced = false; const pointer _Newvec = this->_Getal().allocate(_Newcapacity); _Alty& _Al = this->_Getal(); _TRY_BEGIN _Alty_traits::construct(_Al, _Unfancy(_Newvec + _Oldsize), _STD forward<_Valty>(_Val)...); _Emplaced = true; _Umove_if_noexcept(this->_Myfirst(), this->_Mylast(), _Newvec); _CATCH_ALL if (_Emplaced) { _Alty_traits::destroy(_Al, _Unfancy(_Newvec + _Oldsize)); } _Al.deallocate(_Newvec, _Newcapacity); _RERAISE; _CATCH_END _Change_array(_Newvec, _Newsize, _Newcapacity); } #if _HAS_CXX17 return (this->_Mylast()[-1]); #endif /* _HAS_CXX17 */ }
通过上述代码可以看到,emplace_back的流程逻辑很简单。先检查vector的容量,不够的话就扩容,之后便通过_Alty_traits::construct来创建对象。而最终利用强制类似装换的指针来指向容器类之中对应类的构造函数,并且利用可变长模板将构造函数所需要的内容传递过去构造新的对象。
template<class _Objty, class... _Types> static void construct(_Alloc&, _Objty * const _Ptr, _Types&&... _Args) { // construct _Objty(_Types...) at _Ptr ::new (const_cast<void *>(static_cast<const volatile void *>(_Ptr))) _Objty(_STD forward<_Types>(_Args)...); }
emplace_back这里最为巧妙的部分就是利用可变长模板实现了,任意传参的对象构造。可变长模板是C++11新引进的特性,接下来我们来详细看看可变长模板是如何来使用,来实现任意长度的参数呢?
3.可变长模板与函数式编程
首先,我们先看看,可变长模板的定义:
template <class... T> void f(T... args);
通过template来声明参数包args,这个参数包中可以包含0到任意个参数,并且作为函数参数调用。之后我们便可以在函数之中将参数包展开成一个一个独立的参数。
假设我们有如下需求,需要定义一个max_num函数来求出一组任意参数数字的最大值,在C++11之前的版本或许需要这样去定义这个函数,也就是说我们需要一个参数来指定对应参数的个数,并且这个过程之中存在参数的类型不一致的潜在风险,并不能在编译期进行反馈(不能在编译期进行对于动态语言来说根本不是什么大不了的问题,囧rz):
int max_num(int count, ...) { va_list ap; va_start(ap, count); int ans = va_arg(ap, int); for (int i = 1; i < count; ++i) { int num = va_arg(ap, int); ans = max(ans, num); } va_end(ap); return ans; }
而利用可变长模板,我们可以很优雅地通过以下的代码来实现一个这样的函数:
template<typename t1,typename ...t2> t1 max_num(t1 num, t2 ...args) { auto n = max_num(args...); return n > num ? n : num; } template<typename t1> t1 max_num(t1 num) { return num; }
通过不断递归的方式,提取可变长模板参数之中的首个元素,并且设置递归的终止点的方式来依次处理各个元素。这种处理函数的方式本质上就是在通过递归的方式处理列表,这种编程思路在函数式编程语言之中十分常见,在C++之中看到这样的用法,也让笔者作为C++的入门选手感到很新奇。笔者曾经接触过Scala与Erlang语言之中大量利用了这种写法,但是多层递归导致的必然是栈调用的开销变大,利用尾递归的方式来优化这样的写法,才能减少非必要的函数调用开销。
4.小结
由emplace_back引申出来不少对C++11新特性的探索,笔者也仅仅做一些抛砖引玉的工作。作为程序员,希望大家能够坚持不断动态更新对语言的学习与探索来凝练与高效率的Coding,这也是笔者坚持更新该系列文章的初衷。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
2、【基础环境安装】CentOS6.5 安装Python3.6+python虚拟环境virtualenv安装
自己用Flask写的淘宝天猫优惠券搜索引擎【淘宝券 www.tbquan.cn 】谢谢支持,代码免费领取:http://www.tbquan.cn/share,教程地址:https://www.jianshu.com/c/905dd533e07d CentOS6.5 安装Python3.6+python虚拟环境virtualenv安装 问题描述: CentOS 6.5上默认安装的python版本是2.6.6,现在python3的程序越来越多,所以对python进行升级。 1、下载python(链接:https://www.python.org/ftp/python/3.6.0/Python-3.6.0.tgz )到 /usr/local/目录下 2、以root权限打开终端,进入安装包的存放路径,解压安装包: cd /usr/local/ tar -xzvf Python-3.6.0.tgz 3、进入解压好的安装包文件夹: cd Python-3.6.0 4、编译安装包,指定安装路径,并执行安装命令: 注意:prefix参数用于指定将Python安装在新目录,防止覆盖系统默认安装的pyt...
- 下一篇
CSS动画小结
CSS动画 原理:1.画面之间变化 2.视觉暂留作用 常见问题 1.CSS 动画的实现方式有几种 1.transition 2. keyframes(animation) 2.过渡动画和关键帧动画的区别 1.过渡动画需要状态变化 2.关键帧动画不需要状态变化 3.关键帧动画能控制更精细 3.如何使用逐帧动画 1.使用关键帧动画 2.去掉补间动画(steps) 4.CSS动画性能 1.和 JS 动画很难比较出谁更好 2.部分高危属性(box-shadow) 知识点 动画类型: 1.transition 补间动画 可以计算的属性有:1.位置-平移(left/right/margin/transform) 2.方向-旋转(transform)3.大小-缩放(transform) 4.透明度(opacity)5.其他-线性变换(transform) 示例 在1s 内,宽度有100px 变为 800px 过渡transition是一个复合属性,包括transition-property、transition-duration、transition-timing-function、transiti...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Linux系统CentOS6、CentOS7手动修改IP地址
- 2048小游戏-低调大师作品
- CentOS8安装Docker,最新的服务器搭配容器使用
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16