[置顶] 从零开始学C++之STL(二):实现简单容器模板类Vec(vector capacity 增长问题、allocator 内存分配器)
首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下:
|
1
2 |
template <
class _Ty, class _Ax = allocator<_Ty> > class vector; |
但在VC2008 中vector 还有基类,如下:
|
1 |
// TEMPLATE CLASS vector
template < class _Ty, class _Ax > class vector : public _Vector_val<_Ty, _Ax> { }; |
稍微来看一下基类_Vector_val:
|
1 |
// TEMPLATE CLASS _Vector_val
template < class _Ty, class _Alloc > class _Vector_val : public _CONTAINER_BASE_AUX_ALLOC<_Alloc> { // base class for vector to hold allocator _Alval protected: _Vector_val(_Alloc _Al = _Alloc()) : _CONTAINER_BASE_AUX_ALLOC<_Alloc>(_Al), _Alval(_Al) { // construct allocator from _Al } typedef _Alty _Alval; |
为了理解_Alty 的类型,还得看一下allocator模板类:
|
1 |
template<
class _Ty> class allocator { template<> |
typedef typename _Alloc::template rebind<_Ty>::other _Alty; 整体来看是类型定义,假设现在我们这样使用
vector<int>, 那么_Ty 即 int, _Ax 即 allocator<int>,由vector 类传递给 基类_Vector_val,则_Alloc 即
allocator<int> ;可以看到 allocator<void> 是allocator 模板类的特化, rebind<_Ty> 是成员模板类,other是成员模板类
中自定义类型,_Ty 即是int , 那么other 类型也就是allocator<int>, 也就是说_Alty 是类型 allocator<int> 。
_Alty _Alval; 即 基类定义了一个allocator<int> 类型的成员,被vector 继承后以后用于为vector 里面元素分配内存等操作。
而在VC6.0,_Alval 是直接作为vector 自身的成员存在的。此外还有一个比较大的不同点在于,两个版本对于capacity 也就是容量的
计算方式不同,接下去的测试可以看到这种不同,在这里可以先说明一下:
VC2008:容量每次增长为原先容量 + 原先容量 / 2;
VC6.0 :容量每次增长为原先容量的2倍。
容量跟vector 大小的概念是不一样的,capacity 》= size,如下图所示:
size 指的是avail - data 的区间;capacity 指的是 limit - data 的区间;也就是说存在尚未使用的空间。
下面是模仿VC6.0 中vector 的实现写的Vec 类,程序主要参考《Accelerated C++》 ,略有修改,比如将接口修改成与VC6.0 一致,
这样做的好处是可以传递第二个参数,也就是说可以自己决定内存的分配管理方式;实现capacity() 函数等;
Vec.h:
|
1 |
/*************************************************************************
> File Name: template_class_Vec.h > Author: Simba > Mail: dameng34@163.com > Created Time: Thu 07 Feb 2013 06:51:12 PM CST ************************************************************************/ #include<iostream> template < public: Vec() size_type size() iterator begin() void push_back( private: A_ alloc; // destory the element in the array and free the memory }; template < return * template < template < template < template < alloc.deallocate(data, limit - data); template < // allocate new space and copy elements to the new space // return the old space // reset pointers to point to the newly allocated space template < |
先介绍一下用到的一些类和函数:
allocator 模板类:
|
1 |
#include <memory>
template < class T> class allocator { public: T *allocate(size_t); void deallocate(T *, size_t); void construct(T *, size_t); void destroy(T *); //....... }; |
当然实际的接口没实现没那么简单,但大概实现的功能差不多:
allocate 调用operator new ;deallocate 调用 operator delete; construct 调用placement new (即在分配好的内
存上调用拷贝构造函数),destroy 调用析构函数。
两个std函数:
|
1 |
template <
class In, class For> For uninitialized_copy(In, In, For); template < |
如 std::uninitialized_copy(i, j, data); 即将i ~ j 指向区间的数值都拷贝到data 指向的区间,返回的是最后一个初始化值的下一个位置。
std::uninitialized_fill(data, limit, val); 即将 data ~ limit 指向的区间都初始化为val 。
为了理解push_back 的工作原理,写个小程序测试一下:
|
1 |
#include <iostream>
#include "Vec.h" using class Test int main( return |
从输出可以看出,构造函数调用3次,拷贝构造函数调用6次,析构函数调用9次,下面来分析一下,首先看下图:
首先定义t1, t2, t3的时候调用三次构造函数,这个没什么好说的;接着第一次调用push_back,调用grow进而调用alloc.allocate,
allocate 函数调用operator new 分配一块内存,第一次uncreate 没有效果,接着push_back 里面调用uncheck_append,进而调用
alloc.construct,即调用placement new(new (_Vptr) _T1(_Val); ),在原先分配好的内存上调用一次拷贝构造函数。
接着第二次调用push_back,一样的流程,这次先分配两块内存,将t1 拷贝到第一个位置,调用uncreate(),先调用alloc.destroy,即
调用一次析构函数,接着调用alloc.deallcate,即调用operator delete 释放内存,最后调用uncheck_append将t2 拷贝到第二个位置。
第三次调用push_back,也一样分配三块内存,将t1, t2 拷贝下来,然后分别析构,最后将t3 拷贝上去。
程序结束包括定义的三个Test 对象t1, t2, t3 ,析构3次,Vec<Test> v2; v2是局部对象,生存期到则调用析构函数~Vec(); 里面调用
uncreate(), 调用3次Test 对象的析构函数,调用operator delete 释放3个对象的内存。故总共析构了6次。
在VC2008 中换成 vector<Test> v2; 来测试的话,输出略有不同,如下:
输出的次数是一致的,只是拷贝的顺序有所不同而已,比如第二次调用push_back 的时候,VC2008 中的vector 是先拷贝t2, 接着拷
贝t1, 然后将t1 释放掉。
最后再来提一下关于capacity 的计算,如下的测试程序:
|
1 |
#include <iostream>
#include "Vec.h" using int main( v.push_back( |
输出为 1 2 4 4 8 8 8 即在不够的情况下每次增长为原来的2倍。
如果换成 vector<int> v; 测试,那么输出是 1 2 3 4 6 6 9,即在不够的情况每次增长为原来大小 + 原来大小 / 2;
看到这里,有些朋友会疑惑了,由1怎么会增长到2呢?按照原则不是还是1?其实只要看一下vector 的源码就清楚了:
|
1 |
void _Insert_n(const_iterator _Where,
size_type _Count, const _Ty &_Val) { // insert _Count * _Val at _Where ..... |
_Insert_n 是被push_back 调用的,当我们试图增长为_Capacity + _Capacity / 2; 时,下面还有一个判断:
if (_Capacity < size() + _Count)
_Capacity = size() + _Count;
现在试图增长为 1 + 1/ 2 = 1; 此时因为 1 < 1 + 1 ; 所以 _Capacity = 1 + 1 = 2;
其他情况都是直接按公式增长。
从上面的分析也可以看出,当push_back 的时候往往带有拷贝和析构多个操作,所以一下子分配比size() 大的空间capacity,可以减
轻频繁操作造成的效率问题。
参考:
C++ primer 第四版
Effective C++ 3rd
C++编程规范
[置顶] 从零开始学C++之STL(二):实现简单容器模板类Vec(vector capacity 增长问题、allocator 内存分配器)的更多相关文章
- [转贴]从零开始学C++之STL(二):实现一个简单容器模板类Vec(模仿VC6.0 中 vector 的实现、vector 的容量capacity 增长问题)
首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下: C++ Code 1 2 template < class _Ty, cl ...
- [转贴]从零开始学C++之STL(一):STL六大组件简介
一.STL简介 (一).泛型程序设计 泛型编程(generic programming) 将程序写得尽可能通用 将算法从数据结构中抽象出来,成为通用的 C++的模板为泛型程序设计奠定了关键的基础 (二 ...
- 从零开始学 Web 之 CSS(三)链接伪类、背景、行高、盒子模型、浮动
大家好,这里是「 Daotin的梦呓 」从零开始学 Web 系列教程.此文首发于「 Daotin的梦呓 」公众号,欢迎大家订阅关注.在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识 ...
- 从零开始学Electron笔记(二)
在之前的文章我们简单介绍了一下Electron可以用WEB语言开发桌面级应用,接下来我们继续说一下Electron的菜单创建和事件绑定. 我们接上一章的代码继续编写,上一章代码 https://www ...
- 从零开始学C++之STL(七):剩下5种算法代码分析与使用示例(remove 、rotate 、sort、lower_bound、accumulate)
一.移除性算法 (remove) C++ Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ...
- [置顶] c#验证码识别、图片二值化、分割、分类、识别
c# 验证码的识别主要分为预处理.分割.识别三个步骤 首先我从网站上下载验证码 处理结果如下: 1.图片预处理,即二值化图片 *就是将图像上的像素点的灰度值设置为0或255. 原理如下: 代码如下: ...
- 从零开始学C++之STL(四):算法简介、7种算法分类
一.算法 算法是以函数模板的形式实现的.常用的算法涉及到比较.交换.查找.搜索.复制.修改.移除.反转.排序.合并等等. 算法并非容器类型的成员函数,而是一些全局函数,要与迭代器一起搭配使用. 算法的 ...
- [置顶]
xamarin android使用zxing扫描二维码
好久没写了,这片文章篇幅不长,概述一下在xamarin android中用 ZXing.Net.Mobile库扫描二维码读取url的示例.扫码支付,扫码登录,App上各种各样的扫码,好像没个扫码的就有 ...
- 从零开始学Linux系统(二)之基本操作指令
ifconfigping ip地址帮助:ping -t ip地址ping -c 次数 ip地址ping -s 包的大小关机重启:shutdown -h now reboot清屏:clear == C ...
随机推荐
- ipython的%matplotlib inline如何改写在Python
ipython notebook中有一个相当方便的语句: %matplotlib inline,可以实现运行cell即出现结果图像.但是如果想写在Python程序内,貌似直接%matplotlib i ...
- JS脚本病毒调试脚本-Trojan[Downloader]:JS/Nemucod
1.前言 遇到Trojan[Downloader]:JS/Nemucod需要分析,这款病毒主要为js运行.从网上各种找js调试方法.发现52的帖子还挺沾边的. TrojanDownloader:JS/ ...
- python格式化输出【转】
今天写代码时,需要统一化输出格式进行,一时想不起具体细节,用了最笨的方法,现在讲常见的方法进行一个总结. 一.格式化输出 1.整数的输出 直接使用'%d'代替可输入十进制数字: >>> ...
- 大数据系列之并行计算引擎Spark部署及应用
相关博文: 大数据系列之并行计算引擎Spark介绍 之前介绍过关于Spark的程序运行模式有三种: 1.Local模式: 2.standalone(独立模式) 3.Yarn/mesos模式 本文将介绍 ...
- scp拷贝文件报错-bash: scp: command not found
今天用scp远程传输资料,报错如下: -bash: scp: command not found 在网上搜资料解决办法如下: 安装scp的软件包: # yum install openssh-clie ...
- idea项目左边栏只能看到文件看不到项目结构
1.点击file->project structure..->Modules 点击右上角+加号 ->import Modules 2.选择你的项目,点击确定 3.在如下页面选择imp ...
- iis6 和iis7s上整个网站重定向
iis6 和iis7s上整个网站重定向 重定向作用: 重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置.举例说明:就像我XX公司,之前用的网络域名是“www.bb. ...
- js字符串操作之substr与substring
substr和substring两个都是截取字符串的. 两者有相同点,如果只是写一个参数,两者的作用都是一样的:就是截取字符串当前下标以后直到字符串最后的字符串片段. 例如: `var a=" ...
- mysql innodb 行级锁升级
创建数据表test,表定义如下所示: CREATE TABLE `test` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NO ...
- Java 组合
组合: 在新类中产生现有类的对象,由于新的类是由现有类的对象所组成,所以这种方法称为组合 组合和继承都允许在新的类中放置对象,组合时显示的这样做,而继承则是隐式的这样做 组合技术通常用于想在新类中使用 ...