[置顶] 从零开始学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 ...
随机推荐
- spring如何管理mybatis(一) ----- 动态代理接口
问题来源 最近在集成spring和mybatis时遇到了很多问题,从网上查了也解决了,但是就是心里有点别扭,想看看到底怎么回事,所以跟了下源码,终于发现了其中的奥妙. 问题分析 首先我们来看看基本的配 ...
- 使用 scm-manager 搭建 git/svn 代码管理仓库(二)
主要介绍scm的配置. 1.配置为在Windows服务中启动scm-manager的启动方式有多种,可以在DOS(即命令行CMD模式)中启动,也可以在Windows服务中启动. 下面我们采用Windo ...
- nginx:在centos中自启动
参考网址:http://www.jb51.net/article/120545.htm # vi /etc/init.d/nginx #!/bin/sh # Name:nginx4comex # ng ...
- android 8.0变更
Android 8.0 行为变更 Android 8.0 除了提供诸多新特性和功能外,还对系统和 API 行为做出了各种变更.本文重点介绍您应该了解并在开发应用时加以考虑的一些主要变更. 其中大部分变 ...
- 深入理解java虚拟机-01 走进java
第一章是对java的产生,历史的整体介绍 java的使用很广泛,安装jdk的时候会看到一句广告语runs in 10 billions machines.使用java的设备多达几十亿台 1.概述 优点 ...
- H5新增语义化标签
一.根据页面的结构,由div派生的标签. <header> <footer> <nav> 导航 在H4中导航栏一般用ul-li标签,H5中可以直接用<nav& ...
- 字符串格式化格式 -- Numeric Format Strings
- 微信小程序《沈航二手书》
微信小程序<沈航二手书> 0x01. 利益相关者 利益相关者:是指与客户有一定利益关系的个人或组织群体,可能是客户内部的(如雇员),也可能是客户外部的(如供应商或压力群体). 根据相关利 ...
- [转] caffe视觉层Vision Layers 及参数
视觉层包括Convolution, Pooling, Local Response Normalization (LRN), im2col等层. 1.Convolution层: 就是卷积层,是卷积神经 ...
- ubuntu eclipse 找不到jre文件
一. 把jdk下的jre文件copy到eclipse安装目录 二. 打开eclipse 重新设计library和工作空间