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的知识就介绍到这里

---------------------------------------------------------------分割线-----------------------------------------------------------------

posted @ 2019-08-24 20:10  Alpha205  阅读(317)  评论(0编辑  收藏  举报