C++STL学习笔记(4) 分配器(Allocator)
在前面的博客《C++ STL学习笔记(3) 分配器Allocator,OOP, GP简单介绍》中,简单的介绍了分配器再STL的容器中所担当的角色,这一节对STL六大部件之一的分配器进行详细的学习,从STL源代码的角度去理解分配器的具体细节:
operator new 和malloc
在不同的平台上调用C++中的operator new,最终都会是去调用malloc去分配内存,而malloc又会根据不同的操作系统,去调用相应的API分配内存。malloc分配的内存长什么样子?
其中,size是申请的内存的大小,但是malloc会在此基础上搭配其他的东西,然后将内存地址返回。
灰色部分表示: 在debug_mode 模式下会添加的东西
红色部分表示: 在上下两部分添加的cookie,记录的是分配的内存的大小,因为在free的时候,只是传入了指针,而free就是通过这个区域获得要释放的内存的大小。
绿色部分表示:加上上述部分之后调整到某个边界(这部分只是大概提了一下)
从上图可以看出,malloc实际上返回给我们的东西多余我们申请的东西。
VC6中allocator的实现:
例如,调用allocator分配内存:
int* p = allocator<int>().allocate(512, (int*)0); // 创建一个临时对象 分配内存
allocator<int>().deallocate(p, 512); // 释放内存
Allocator对于内存的分配会有额外的开销,如上面上所述,实际分配的内存大于所需的内存(添加了额外的部分),Gnu2.9所附的标准库中,有一个实现更优的分配器alloc,它的行为模式如下:
Gnu的分配器为了实现节省内存,他需要减少malloc的次数,所以设计了16条链表,每条链表负责不同大小的区块,第0条链表负责的是8字节大小区块,第一条负责16字节大小的区块,以此类推....所以在容器需要内存的时候,内存中元素的大小会被分配器调整到8的倍数,然后再具体确定选择哪一条链表。
分配器主要是为容器服务/
容器结构与分类:
适配器的理解:
deque --> stack, queue相当于deque的适配器,因为stack, queue中用到了deque,所以将它们称为适配器更加合适。
1. 容器list
节点的设计:
关于迭代器:
扮演指针的功能,定义为一个类:
对于vector,array这种连续内存的容器的迭代器,和其他内存不连续的对象的迭代器,是有一定的区别的。除此之外迭代器需要重载->, *, ++, --等运算符。
Iterator需要遵循的原则:
traits: 人为设计的一种萃取机,萃取出特征,有type_traits, character_traits, pointer_traits, 那么iterator_traits又是什么呢?
所以iterator_traits的作用是萃取出iterator的特征,那么iterator有什么特性呢?
前面讲过,iterator是算法和容器之间的桥梁,它让算法知道要处理的元素的范围,在算法知道要处理的元素的范围后,算法需要对元素进行一些操作,在这些操作的过程中,算法很可能需要知道这个iterator有哪些性质,从而选择一个最佳化的动作对元素进行操作,例如:
举个例子,有一个rotate函数,
可以看到rotate 函数调用了std::_rotate 和std::_iterator_category
可以看到std::_iterator_category的定义:
可以看到std::_iterator_category中调用了iterator_traits<Iter>::iterator_category,可以看到这是要萃取迭代器Iter的iterator_category特征,也就是迭代器的分类(关于迭代器的分类前面讲过,有输入迭代器,输出迭代器,前向迭代器,双向迭代器,以及random access迭代器)。
Input iterator(输入迭代器) | 读,不能写 | 只支持自增运算 |
Output iterator(输出迭代器) ; | 写,不能读 | 只支持自增运算 |
Forward iterator(前向迭代器) | 读和写; | 只支持自增运算 |
Bidirectional iterator(双向迭代器) ; | 读和写 | 支持自增和自减运算 |
Random access iterator(随机访问迭代器) | 读和写; | 支持完整的迭代器算术运 算 |
(查了一下我没记错,就是这五种)
所以说,rotate算法知道了迭代器的分类,以便他能够采取最佳的操作方式,
接着看std::_rotate函数的定义:
可以看到,这里也调用了iterator_traits,分别获迭代器(random access)的value_type, 以及difference_type
value_type: iterator所指向的元素的类型
difference_type: 两个迭代器之间的距离,应该用什么数据类型来表现
(C++标准库中,共有五种迭代器特性,除了上面的terator_category, value_type, value_type, 还有reference_type以及pointer_type两种特征, 称之为迭代器的associative types,相关类型 , 作为一个迭代必须设计出这五种特性,以便能够回答算法的提问。)
可以看到,在rotate算法中,它需要知道iterator的三个特征。
举一个例子,list的迭代器的实现如下所示:
可以看到。它实现了迭代器的5种associative types, 能够满足算法的提问。
算法中对iterator的五种类型的获取:
如果算法接收到的迭代器不是class,而是一个native pointer(也就是C++中的自然指针,这也是C++迭代器,退化的迭代器),那么此时迭代器是如何回答算法的提问的呢?
C++通过添加一个中间层(萃取机)来解决这一问题:(侯捷老师说:解决计算机问题的尚方宝剑:添加一个中介层)
所以iterator_traits用来分离class_iterators和non_class_iterators:
可以看到iterator的定义如下,出了泛化的定义之外,还有特化的版本:
从上面的iterator_traits的定义可以看到它作为一个中间层的分离功能。
可以顺便看一下迭代器完整的定义:
在C++ STL中,还有其他的各种Traits
关于traits的知识就介绍到这里
---------------------------------------------------------------分割线-----------------------------------------------------------------