首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下:

 C++ Code 
1
2
 
template < class _Ty, class _Ax = allocator<_Ty> >
class vector;

但在VC2008 中vector 还有基类,如下:

 C++ Code 
1
2
3
4
5
6
7
 
// TEMPLATE CLASS vector
template < class _Ty,
         class _Ax >
class vector
    : public _Vector_val<_Ty, _Ax>
{
};

稍微来看一下基类_Vector_val:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
// 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 typename _Alloc::template
    rebind<_Ty>::other _Alty;

_Alty _Alval;   // allocator object for values
};

为了理解_Alty 的类型,还得看一下allocator模板类:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
template<class _Ty> class allocator
{

template<> class _CRTIMP2_PURE allocator<void>
    {
        // generic allocator for type void
    public:
        template<class _Other>
        struct rebind
        {
            // convert an allocator<void> to an allocator <_Other>
            typedef allocator<_Other> other;
        };
        ....
    };
    ...
};

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 里面元素分配内存等操作。

iterator new_data  = alloc.allocate(new_size);
注意,标准的vector::iterator 是以模板类实现的,下面的实现简单地将其等同为指

针,实际上真正的iterator
类的实现是内部有一个指针成员,指向容器元素。

××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

对比list 的实现,继承它的基类_List_nod 的一个成员 allocator<_Node>   _Alnod;  如下:

typename _Alloc::template rebind<_Node>::other  _Alnod;   //
allocator object for nodes

其中 _Node有三个成员,如下:

_Nodeptr _Next;  // successor node, or first element if head
_Nodeptr _Prev; // predecessor node, or last element if head
_Ty _Myval; // the stored value, unused if head

如果是list<int> ,那么_Ty 即是int 类型。双向链表在创建一个新结点时,这样实现:

_Nodeptr _Pnode = this->_Alnod.allocate(1); // 即分配一个节点的空间,返回指向这个节点的指针。

实际上list 还继承另外两个基类的两个成员,如下:

typename _Alloc::template rebind<_Nodeptr>::other  _Alptr;// allocator object for pointers to nodes

typename _Alloc::template rebind<_Ty>::other _Alty  _Alval;  // allocator object for values stored in nodes

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

在VC6.0 vector 的实现,_Alval 是直接作为vector 自身的成员存在的。此外还有一个比较大的不同点在于,两个版本对于capacity 也就是容量的

计算方式不同,接下去的测试可以看到这种不同,在这里可以先说明一下:

VC2008:容量每次增长为原先容量 + 原先容量 / 2;

VC6.0 :容量每次增长为原先容量的2倍。

容量跟vector 大小的概念是不一样的,capacity 》= size,如下图所示:

size 指的是avail  - data 的区间;capacity 指的是 limit - data 的区间;也就是说存在尚未使用的空间。

******************************************************************************************************************************************************

data, avail 和 limit 是vector 类的三个指针成员,对比list 的实现,自身也许只有两个成员,如下:
Nodeptr _Myhead; // pointer to head node
size_type _Mysize; // number of elements

实际上 list不仅是一个双向链表,而且还是一个环状双向链表。另外,插入操作和结合操作都不会造成原有的list迭代器失效,这在vector是不成立的。

因为vector的插入操作可能造成存储空间重新配置,导致原有的迭代器全部失效。list的元素删除操作(erase),也只有“指向被删除元素”的那个

迭代器失效,其他迭代器不受任何影响。

*******************************************************************************************************************************************************

下面是模仿VC6.0 中vector 的实现写的Vec 类,程序主要参考《Accelerated C++》 ,略有修改,比如将接口修改成与VC6.0 一致,

这样做的好处是可以传递第二个参数,也就是说可以自己决定内存的分配管理方式;实现capacity() 函数等;

Vec.h:

 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
 
/*************************************************************************
> 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>
#include<cstddef>
#include<memory>
#include<algorithm>

template < class T, class A_ = std::allocator<T> >
class Vec
{

public: // interface
    typedef T *iterator;
    typedef const T *const_iterator;
    typedef size_t size_type;
    typedef T value_type;
    typedef std::ptrdiff_t difference_type;
    typedef T &reference;
    typedef const T &const_reference;

Vec()
    {
        create();    // default constructor
    }
    // const T & t = T();意思是默认参数,当没有传递 参数时,T() 即定义一个类型为T的无名对象(参数为空会调用默认构造函数)

// 也就是 t 引用着这个无名对象。
    //explicit表示不允许构造函数进行隐式类型转换
    explicit Vec(size_type n, const T &val = T())
    {
        create(n, val);
    }
    Vec(const Vec &v)
    {
        create(v.begin(), v.end());    // copy constructor
    }
    Vec &operator=(const Vec &);  // assigment operator
    ~Vec()
    {
        uncreate();    // destructor
    }

size_type size() const
    {
        return avail - data;    // a value of ptrdiff_t
    }
    size_type capacity() const
    {
        return (data == 0 ? 0 : limit - data);
    }
    T &operator[](size_type i)
    {
        return data[i];
    }
    /* because their left operand is different(const), we can overload the operation */
    const T &operator[](size_type i) const
    {
        return data[i];
    }

iterator begin()
    {
        return data;
    }
    const_iterator begin() const
    {
        return data;
    }
    iterator end()
    {
        return avail;
    }
    const_iterator end() const
    {
        return avail;
    }

void push_back(const T &val)
    {
        if (avail == limit) // get space if needed
            grow();
        unchecked_append(val); // append the new element
    }
    void clear()
    {
        uncreate();
    }
    bool empty()
    {
        return data == avail;
    }

private:
    iterator data; // first element in the Vec
    iterator avail; // one past the last constructed element in the Vec
    iterator limit; // one past the last available element

A_ alloc; // object to handle memory allocation
    // allocate and initialize the underlying array
    void create();
    void create(size_type, const T &);
    void create(const_iterator, const_iterator);

// destory the element in the array and free the memory
    void uncreate();
    // support functions for push_back
    void grow();
    void unchecked_append(const T &);

};

template < class T, class A_>
Vec<T, A_> &Vec<T, A_>::operator=(const Vec<T, A_> &rhs)
{
    // check for self-assigment
    if (&rhs != this)
    {
        uncreate();
        create(rhs.begin(), rhs.end());
    }

return *this;
}

template < class T, class A_>
void Vec<T, A_>::create()
{
    data = avail = limit = 0;
}

template < class T, class A_>
void Vec<T, A_>::create(size_type n, const T &val)
{
    data = alloc.allocate(n);
    limit = avail = data + n;
    std::uninitialized_fill(data, limit, val);
}

template < class T, class A_>
void Vec<T, A_>::create(const_iterator i, const_iterator j)
{
    data = alloc.allocate(j - i);
    limit = avail = std::uninitialized_copy(i, j, data);
    /* return a pointer to (one past) the last element that it initialized */
}

template < class T, class A_>
void Vec<T, A_>::uncreate()
{
    if (data)
    {
        // destroy(in reverse order) the elements that were constructed
        iterator it = avail;
        while (it != data)
            // destory runs T's destructor for that object, rendering the storage uninitialized again
            alloc.destroy(--it);

alloc.deallocate(data, limit - data);
    }
    // reset pointers to indicate that Vec is empty again
    data = limit = avail = 0;
}

template < class T, class A_>
void Vec<T, A_>::grow()
{
    // when growing, allocate twice as much space as currently in use
    size_type new_size = std::max(2 * (limit - data), ptrdiff_t(1));

// allocate new space and copy elements to the new space
    iterator new_data = alloc.allocate(new_size);
    iterator new_avail = std::uninitialized_copy(data, avail, new_data);

// return the old space
    uncreate();

// reset pointers to point to the newly allocated space
    data = new_data;
    avail = new_avail;
    limit = data + new_size;
}

template < class T, class A_>
// error C4519: 仅允许在类模板上使用默认模板参数
void Vec<T, A_>::unchecked_append(const T &val)
{
    alloc.construct(avail++, val);
}

先介绍一下用到的一些类和函数:

allocator 模板类:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
 
#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函数:

 C++ Code 
1
2
3
4
5
 
template < class In, class For>
For uninitialized_copy(In, In, For);

template < class For, class T>
void uninitialized_fill(For, For, const T &);

std::uninitialized_copy(i, j, data); 即将i ~ j 指向区间的数值都拷贝到data 指向的区间,返回的是最后一个初始化值的下一个位置。

std::uninitialized_fill(data, limit, val);  即将 data ~ limit 指向的区间都初始化为val 。

 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
28
29
30
31
32
33
34
 
#include <iostream>
#include "Vec.h"

using namespace std;

class Test
{
public:
    Test()
    {
        cout << "Test ..." << endl;
    }
    Test(const Test &other)
    {
        cout << "copy Test ..." << endl;
    }
    ~Test()
    {
        cout << "~Test ..." << endl;
    }
};

int main(void)
{
    vector<Test> v2;
    Test t1;
    Test t2;
    Test t3;
    v2.push_back(t1);
    v2.push_back(t2);
    v2.push_back(t3);

return 0;
}

alloc.construct,即调用placement
new(new (_Vptr) _T1(_Val); ),在原先分配好的内存上调用一次拷贝构造函数。

调用一次析构函数,接着调用alloc.deallocate,即调用operator delete 释放内存,最后调用uncheck_append将t2
拷贝到第二个位置。

第三次调用push_back,也一样分配三块内存,将t1, t2 拷贝下来,然后分别析构,最后将t3 拷贝上去。

程序结束包括定义的三个Test 对象t1, t2, t3 ,析构3次,Vec<Test> v2;  v2是局部对象,生存期到则调用析构函数~Vec(); 里面调用

在VC2008 中换成 vector<Test> v2; 来测试的话,输出略有不同,如下:

输出的次数是一致的,只是拷贝的顺序有所不同而已,比如第二次调用push_back 的时候,VC2008 中的vector 是先拷贝t2, 接着拷

贝t1, 然后将t1 释放掉。

 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
 
#include <iostream>
#include "Vec.h"

using namespace std;

int main(void)
{
    Vec<int> v;

v.push_back(1);
    cout << v.capacity() << endl;
    v.push_back(1);
    cout << v.capacity() << endl;
    v.push_back(1);
    cout << v.capacity() << endl;
    v.push_back(1);
    cout << v.capacity() << endl;
    v.push_back(1);
    cout << v.capacity() << endl;
    v.push_back(1);
    cout << v.capacity() << endl;
    v.push_back(1);
    cout << v.capacity() << endl;
}

输出为 1 2 4 4 8 8 8 即在不够的情况下每次增长为原来的2倍。

如果换成 vs2008 下 vector<int> v; 测试,那么输出是 1 2 3 4 6 6 9,即在不够的情况每次增长为原来大小 + 原来大小 / 2;

看到这里,有些朋友会疑惑了,由1怎么会增长到2呢?按照原则不是还是1?其实只要看一下vector 的源码就清楚了:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
void _Insert_n(const_iterator _Where,
               size_type _Count, const _Ty &_Val)
{
    // insert _Count * _Val at _Where

.....
    size_type _Capacity = capacity();
    .....
    else if (_Capacity < size() + _Count)
    {
        // not enough room, reallocate
        _Capacity = max_size() - _Capacity / 2 < _Capacity
                    ? 0 : _Capacity + _Capacity / 2;    // try to grow by 50%
        if (_Capacity < size() + _Count)
            _Capacity = size() + _Count;
        pointer _Newvec = this->_Alval.allocate(_Capacity);
        pointer _Ptr = _Newvec;
        .....
    }
}

2;
 时,下面还有一个判断:

if (_Capacity < size() + _Count)

_Capacity = size() + _Count;

_Capacity = 1 + 1 = 2;

轻频繁操作造成的

效率问题。

参考:

C++ primer 第四版
Effective C++ 3rd
C++编程规范

Accelerated C++

实现简单容器模板类Vec(vector capacity 增长问题、allocator 内存分配器)的更多相关文章

  1. [转贴]从零开始学C++之STL(二):实现一个简单容器模板类Vec(模仿VC6.0 中 vector 的实现、vector 的容量capacity 增长问题)

    首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下:  C++ Code  1 2   template < class _Ty, cl ...

  2. [置顶] 从零开始学C++之STL(二):实现简单容器模板类Vec(vector capacity 增长问题、allocator 内存分配器)

    首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下:  C++ Code  1 2   template <  class _Ty,  ...

  3. C++(四十四) — 数组模板类(vector工具)

    实现 stl 中的 vector 操作. 1.MyVector.h #pragma once #include <iostream> using namespace std; templa ...

  4. C++之vector模板类

    vector 称为容器模板类,是同一种类型的对象的集合,每个对象都有一个对应的整数索引值.vector 不是一种数据类型,而只是一个类模板,可用来定义任意多种数据类型.vector 类型的每一种都指定 ...

  5. iOS Xcode制作模板类-b

    为什么要定义模板类 遵守代码规范可以提高代码可读性, 降低后期维护成本. 当我们定下了一个团队都认同的代码规范, 如我们要求所有的viewController的代码都得按照下面来组织: #pragma ...

  6. iOS Xcode制作模板类

    转载请注明出处http://blog.csdn.net/uxyheaven/article/details/48419963 为什么要定义模板类 遵守代码规范可以提高代码可读性, 降低后期维护成本. ...

  7. QCache 缓存(类似于map的模板类,逻辑意义上的缓存Cache,方便管理,默认类似于LRU的淘汰算法)

    最近在学习缓存方面的知识,了解了缓存(Cache)的基本概念,为什么要使用缓存,以及一些缓存算法(缓存替换),如LRU.LFU.ARC等等. 这些缓存算法的实现过程会使用一些基本的数据结构,如list ...

  8. C++定义一个简单的Computer类

    /*定义一个简单的Computer类 有数据成员芯片(cpu).内存(ram).光驱(cdrom)等等, 有两个公有成员函数run.stop.cpu为CPU类的一个对象, ram为RAM类的一个对象, ...

  9. 模板类 vector

    概要 介绍一下模板类 vector 的常用操作,以及一个应用举例,顺时针打印矩阵.   基本定义 模板类 vector 是一种动态数组,它是使用 new 创建动态数组的替代品,实际上,vector 也 ...

随机推荐

  1. Pylons Controller里面Session.commit()总是出现rollback

    Pylons Controller里面执行修改数据库表,总是不成功. 然后通过各种手段: 1.js打印返回值 2.不修改表单提交和修改表单提交,结果比较: 3.通过在Pylons Controller ...

  2. linux下svn的用法

    转载:http://blog.chinaunix.net/uid-22150747-id-189264.html 1.将文件checkout到本地目录 svn checkout path(path是服 ...

  3. Ext Connection

    <html> <head> <meta http-equiv="Content-Type" content="text/html; char ...

  4. 使用 pm2-web 监控 pm2 服务运行状态

    pm2-web 是一款 pm2 服务状态监控程序,基于 web . 安装 $ npm install -g pm2-web 运行(默认是在8080端口) $ pm2-web 配置 pm2-web 将会 ...

  5. 幸福框架:用户想看到的操作日志也要使用AOP吗?

    背景 日志无论是对于开发人员.运维人员和最终用户都是一笔财富,是不是所有类型的日志都要AOP呢?本着交流的目的,这里先说一些看法,希望大家多批评. 常见的日志类型 异常日志 概念:记录异常的日志. 考 ...

  6. SharePoint 2013 代码创建应用程序目录(App Catalog)

    众所周知,SharePoint App是2013版本的一大特色,那么,关于App的分发有几种方式呢?SharePoint给我们提供了两种方式,一种是上载到SharePoint应用商店,另一种是在本地S ...

  7. U盘安装Centos7.0图解

    用U盘安装Centos7.0 工具/原料 centos 7.0 64位系统:CentOS-7.0-1406-x86_64-DVD.iso ultraiso 一个至少8G的U盘 方法/步骤 1 1.先使 ...

  8. 《C++反汇编与逆向分析技术揭秘》之十——析构函数

    局部对象 当对象所在作用域结束之后,销毁栈空间,此时析构函数被调用. 举例: 函数返回时自动调用析构函数: 堆对象 调用析构代理函数来处理析构函数: 为什么使用析构代理函数来调用析构函数?考虑到如果d ...

  9. (转)Sql Server 快速查看表结构(表描述及字段说明)

    --表描述 SELECT tbs.name 表名,ds.value 描述 FROM sys.extended_properties ds LEFT JOIN sysobjects tbs ON ds. ...

  10. 嵌入式mp3播放器

    分四部分:按键驱动,声卡驱动,Madplay播放器移植,MP3主播放器处理 按键1:播放,按键2:停止,按键3:上一曲,按键4:下一曲 UA1341内核自带声卡驱动 .解压内核: tar zxvf l ...