C++ STL和泛型编程(二)----分配器(allocators)
一、OOP vs. GP
- OOP: Object-Oriented programming
OOP一般将数据和操作数据的函数放在同一个类里进行,然后再通过各种继承关系进行运作。
这里list没有RandomAccessIterator,所以不能用全局的::sort()进行排序,因为全局的sort()要求传入的指针参数,可以随意加减变化的,而list容器里的指针只能是由上一个指向下一个而不能随意操作如*(first+(last-first)/2)。因此list的sort()要自己在类内定义声明。
- GP: Generic Programming
GP将da’ta和操作data的methods分开来,容器vector存放数据,若需要对容器里的数据进行排序,则通过迭代器beging(),end() 来 调用算法::sort()进行操作。
这里容器vector和deque都提供RandomAccessIterator,所以可以直接调用全局的::sort()函数,且这里::sort()重写了两个版本。
Containers和Algorithms团队可各自闭门造车,以Iterator沟通即可;
Algorithms通过Iterators确定操作范围,并通过Iterators取用Container元素。
Algorithms:
二、分配器allocators
无论在何种编译器下,allocators最终底层调用的都是operator new()\operatore delete(),而更底层的调用则是malloc()/free()。
而malloc()所分配的内存大小,实际上是有额外的东西的,如cookie等额外的开销,所以分配器的各种定义使用影响分配空间的效率:
- (1)在VC下:
-
对allocator的使用:
-
底层实现
从底层可以看到,调用的是operator new(),最终调用的是malloc()。
由pointer allocate(size_type _N, const void*)知,若想不通过容器直接调用allocator,则传入参数应为元素个数以及对应的一个空指针类型参数。所以为int* p = allocator().allocate(512,(int*)0);
这里 类型名+() 是表示创建一个临时对象的意思,allocator()。
- (2)在BC下:
-
对allocator的使用
-
底层实现
这里绕过容器直接使用allocator时,无需指定第二参数,因为其默认值为0:
pointer allocate(size_type n, allocator<void>::const_pointer = 0 )
则 int* p = allocator<int>().allocate(512)
- (3)在G2.9下:
- 标准库定义
- 对allocator的使用:
虽然在G2.9下定义了allocate,但实际上使用时用的是alloc。
void* p = alloc::allocate(512)或alloc().allocate(512);
1.alloc实现如下,设计了16条链表,其目的是事先malloc好一个带有额外信息如cookie的空间,而且每条链表负责为8的倍数关系,#0表示大小为8,#1表示大小为16,…,#15表示大小为128(16*8)。所有容器需要内存时,都会来向alloc要内存。
2.因为malloc是给需要分配不同大小的内存空间的,所以malloc需要包含cookie等信息的额外空间开销;但是在容器中,因为每个元素大小是一致的,所以在考虑是否可以尽量少地调用malloc。因为放进容器地元素大小相同,可考虑尽量省去cookie等信息。
3.若此时容器需要放入地元素大小为50,则调整为56,然后找下#6链表有没挂内存块,如果没有则调用malloc()分配一个带cookie等额外开销的内存,然后切割成一份份用单向链表串起来,所以切出来的每一小块内存块都不带cookie。用这些内存块来放置元素,这样就不用为容器的每一个元素都malloc出包含cookie等信息额外空间开销的内存。
- (4)在G4.9下:
- 对allocator的使用:
- 底层的使用:
在G4.9下的alloc变成了__pool_alloc:
则在G4.9下想调用alloc时,可指定第二参数即allocator为 __gnu_cxx::__pool_alloc<string> :
vector<string, __gnu_cxx::__pool_alloc<string>> vec;