目录
分配器
default allocator
GI STL 的头文件defalloc.h中有一个符合标准的名为allocator的内存分配器,它只是简单地将::operator new 和::operator delete做了一层薄薄的封装。在SGI STL的容器和算法部分从来没有用到这个内存分配器。在此略过。
STL 的内存分配策略
当用户用new构造一个对象的时候,其实内含两种操作:1)调用::operator new申请内存;2)调用该对象的构造函数构造此对象的内容
当用户用delete销毁一个对象时,其实内含两种操作:1)调用该对象的析构函数析构该对象的内容;2)调用::operator delete释放内存
SGI STL中对象的构造和析构由::construct()和::destroy()负责;内存的申请和释放由alloc:allocate()和alloc:deallocate()负责;此外,SGI STL还提供了一些全局函数,用来对大块内存数据进行操作。
上一段提到的三大模块分别由stl_construct.h stl_alloc.h stl_uninitialized.h 负责
VC6.0下的allocator没有优化
// newop operator new(size_t) for Microsoft C++
#include <cstdlib>
#include <new>
#include <xutility>
_C_LIB_DECL
int __cdecl _callnewh(size_t count) _THROW1(_STD bad_alloc);
_END_C_LIB_DECL
void *__CRTDECL operator new(size_t count) _THROW1(_STD bad_alloc)
{ // try to allocate size bytes
void *p;
while ((p = malloc(count)) == 0)
if (_callnewh(count) == 0)
{ // report no memory
_STD _Xbad_alloc();
}
return (p);
}
/*
* Copyright (c) 1992-2007 by P.J. Plauger. ALL RIGHTS RESERVED.
* Consult your license regarding permissions and restrictions.
V5.03:0009 */
allocator
主要的两个操作是:allicate
和deallocate
;其里面调用operator new
和operator delete
。- 没必要自己手动去写
int *p=allocator<int>().allocate(512,(int*)0)
;和allocator<int>().deallocate(p,512)
。要写明释放内存的大小,所以很难用,但是容器不需要考虑这些
VC版本如下
缺点:调用operator new 的获取内存,每次都是获取一点点内存,这样会有很多的额外开销
BC版本:
Gnu 2.9版本:
发现:这三个IDE都是通过malloc和free来分配内存,因此会带来大量的额外开销
SGI STL不使用这个allocator
STL使用alloc
: (目的:尽量减少malloc次数)
16条链表,每条链表下面申请空间,额外的空间是Cookie,记录内存的大小
Gnu 4.9版下:
- 使用的allocator,没有使用alloc;为什莫不用,不知道
G4.9所附的标准库中,有许多extention allocators,其中_pool_alloc
就是G2.9的alloc
.
容器直接的关系与分类
- 依缩进的方式表示复合关系,有一个关系;比如:
priority_queue
里面有一个heap
支撑。- 旁边为指针大小
深度探索list
list Gnu2.9
源代码实现- 注意
node
代码和图示的位置实现前闭后开,增加一个空白节
- 用的分配器alloc
Iterator
智能指针,需要知道结点node的next指针- 除了array和vector,其他容器的iterator都是一个class
iterator
class 实现
- 必须做至少5个
typedef
,很多操作符重载- 前++
++i
其中i作为对象,self& operator++(){}
返回引用,这样可以进行两次前++- 后++
i++
self operator++(int){}
,阻止两次后++++++i
对的++(++i)
;不允许两次后++(i++)++
list
的迭代器解引用,不像vetor直接是数据,而是(*iter).data
Gnu 4.9
的改善地方,下图中有说明:链表指向自己
- G4.9类之间的关系变得复杂
迭代器设计原则和Iterator Trails的作用和设计
Iterator
需要遵循的原则,必须提供5种associated types
- 算法向iterator提问,获得一些类型type,方便处理
问答式
Traits
特性,特质
- 算法的参数,可能是iterator或者普通指针,通过中间层traits实现
算法执行时,根据传入的参数,选择相应的
iterator_traits
标准库中还有其他的traits:
type traits , char traits, allocator traits, pointer traits, array traits
等
vector深度探索
vector
内存2倍扩充,是在另外的地方重新申请内存,将数据搬过去前闭后开区间
- 2倍扩充方法,reallocation
insert_aux
函数中再次判断,原因是可能有其他放元素的操作如insert
也要拷贝插入之后的数据,考虑到
insert
插入某个位置,红色框部分
vector's iterator
vector
结点是连续的,iterator
不需要是类,是个指针即可。- 算法问的五种相关类型,通过
iterator traits
实现- 通过指针偏特化
G4.9 vetor and iterator
- 变得复杂,但是追踪其根源和G2.9本质一样。
array,foward_list深度探索
容器array
TR1
技术报告1 ,C++1.0-1998和c++2.0-2011之间的过渡版本- 源码很清晰,没有构造和析构函数;申明数组大小;用指针当迭代器
G4.9
变得复杂
容器forward-list
deque,queue和stac深度探索
deque
duque
内存结构- 分段连续,用户看起来是连续的
- 迭代器为了维持连续的状态,每次++,--都需要判断当前buffer是否用完,若用完则需要通过控制中心跳到下一个buffer
控制中心的map是指向vector的,以后也是2倍增长
deque iterator
迭代器实现,关键有指向map的指针
deque<T>::insert
讲解- 很好体现deque的灵活性
要判断插入位置离首尾那个近一些,插入操作涉及元素搬移
- deque如何实现连续空间
- 主要是迭代器运算符重载实现
- 用后++(i++)调用前++(++i)
前++(++i)其中i作为对象,self& operator++(){}返回引用,这样可以进行两次前++ 后++ (i++) self operator++(int){},阻止两次后++ ++++i对的++(++i);不允许两次后++ (i++)++
G4.9
实现- 控制中心实际为vector,当不足的时候,成两倍的增长,此时copy数据到新vector的中心位置,这样方便两边数据的增长
queue,stack
的实现
- 底层用deque实现,实际操作调用deque的函数
有时不把queue,stack当作容器
- stack,queue都不允许遍历,不提供iterator
- stack,queue也可以选择list做底层实现,默认选择duque做底层
queue不可选择vector做底层实现,stack也可以用vector做底层实现
编译器不会对容器做全面的检查,当没有调用错误的函数时候,编译器不会报错!
RB-tree深度探索
Red-Black tree是自平衡二叉搜索树。
- rb_tree的封装
- 清楚传入模板的参数列表;然后构建了一个虚空结点header
- KeyOfValue表示怎么从value中取出key
identity
函数(Gnu C独有)就是表示同一个数的意思
- handle-body,采用OOP的思想,构建G4.9
一个红黑树的大小为4个字节
- test Rb_tree
#include <set> #include <functional> #include <iostream> namespace jj31 { void test_Rb_tree() { //G2.9 vs. G2.9 : //rb_tree => _Rb_tree, //identity<> => _Identity<> //insert_unique() => _M_insert_unique() //insert_equal() => _M_insert_equal() cout << "\ntest_Rb_tree().......... \n"; _Rb_tree<int, int, _Identity<int>, less<int>> itree; cout << itree.empty() << endl; //1 cout << itree.size() << endl; //0 itree._M_insert_unique(3); itree._M_insert_unique(8); itree._M_insert_unique(5); itree._M_insert_unique(9); itree._M_insert_unique(13); itree._M_insert_unique(5); //no effect, since using insert_unique(). cout << itree.empty() << endl; //0 cout << itree.size() << endl; //5 cout << itree.count(5) << endl; //1 itree._M_insert_equal(5); itree._M_insert_equal(5); cout << itree.size() << endl; //7, since using insert_equal(). cout << itree.count(5) << endl; //3 } }
set,multiset深度探索
set、multiset元素的value和key合一,value就是key.
- 容器set实现
- const_iterator实现set不能改变容器元素的值
- 使用identity表示set已经知道key和value是相同的
map,multimap深度探索
map/multimap的iterator不能改变key,可以改变value
- map的结构
- pair将key和data合成value;将key设置为const,这样通过迭代器就不会改变key的值。
select1st实现
- map容器独特的operator[]操作,可以进行插入操作
- 直接调用insert快一些
hashtable深度探索
- hashtable冲突(碰撞)处理
- rehash时,篮子扩充两倍,找到其附近的质数,重新计算元素位置
- 内部扩充的数据已经预定好,53->97->....
- hashtable实现
- iterator要实现当当前node链表结束,要能进入到下一个buckets
- hashtable使用
- 模板参数的形式
- 容器hashtable中hashfunction
- hash{}的偏特化实现
hashtable使用
unordered容器概念
结构
- test unordered_set
#include <unordered_set> #include <stdexcept> #include <string> #include <cstdlib> //abort() #include <cstdio> //snprintf() #include <iostream> #include <ctime> namespace jj15 { void test_unordered_set(long& value) { cout << "\ntest_unordered_set().......... \n"; unordered_set<string> c; char buf[10]; clock_t timeStart = clock(); for(long i=0; i< value; ++i) { try { snprintf(buf, 10, "%d", rand()); c.insert(string(buf)); } catch(exception& p) { cout << "i=" << i << " " << p.what() << endl; abort(); } } cout << "milli-seconds : " << (clock()-timeStart) << endl; cout << "unordered_set.size()= " << c.size() << endl; cout << "unordered_set.max_size()= " << c.max_size() << endl; //357913941 cout << "unordered_set.bucket_count()= " << c.bucket_count() << endl; cout << "unordered_set.load_factor()= " << c.load_factor() << endl; cout << "unordered_set.max_load_factor()= " << c.max_load_factor() << endl; cout << "unordered_set.max_bucket_count()= " << c.max_bucket_count() << endl; for (unsigned i=0; i< 20; ++i) { cout << "bucket #" << i << " has " << c.bucket_size(i) << " elements.\n"; } string target = get_a_target_string(); { timeStart = clock(); auto pItem = find(c.begin(), c.end(), target); //比 c.find(...) 慢很多 cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl; if (pItem != c.end()) cout << "found, " << *pItem << endl; else cout << "not found! " << endl; } { timeStart = clock(); auto pItem = c.find(target); //比 std::find(...) 快很多 cout << "c.find(), milli-seconds : " << (clock()-timeStart) << endl; if (pItem != c.end()) cout << "found, " << *pItem << endl; else cout << "not found! " << endl; } } }