(原创滴~)STL源码剖析读书总结1——GP和内存管理

读完侯捷先生的《STL源码剖析》,感觉真如他本人所说的“庖丁解牛,恢恢乎游刃有余”,STL底层的实现一览无余,给人一种自己的C++水平又提升了一个level的幻觉,呵呵~

纵观全书,讲得最多、最透彻的就是C++的Generic Programming的思想。STL通过class template,function template将容器、迭代器、算法从数据类型中解脱出来,使其能“以不变应万变”,并能针对不同数据类型的相关特性(type_traits),从各种版本的实现中选最优而用之,从具体到抽象,又兼具具体特性的考虑,实乃智慧的结晶。这本书也开阔了我的眼界,使我见识了如partial specialization(偏特化),Specialization and Generalization,以偏特化的方式使C++具备type of的能力等的语言运用的现象。下面就先来讲一下class template partial specialization(类模板偏特化):

  • 1. 偏特化

所谓偏特化就是我们在设计class template的时候,除了提供必要的一般化设计之外,还设计了其它比较特殊的版本(比如接受T*为参数等等)。这些版本跟一般化的版本相比,要特殊一些,但有没有丢失什么类型都要的一般性,所以称之为部分特殊化或者偏特化。我们在实例化一个class template的时候,C++会根据传入的template参数类型,自动选取匹配程度最高的template进行实例化,举个栗子:

// 一般化设计

template <class I, class O>

struct testClass

{

testClass() { cout << "I, O" << endl; }

};

// 偏特化版本1

template <class T>

struct testClass< T*, T*>

{

testClass() { cout << "T*, T* " << endl; }

};

// 偏特化版本2

template <class T>

struct testClass<const T*, T*>

{

testClass() { cout << "const T*, T* "<<endl; }

};

int main()

{

testClass<int, char> obj1; // I, O

testClass<int*, int*> obj2; // T*, T*

testClass<const int*, int*> obj3; // const T*, T*

}

  • 2. STL的内存管理

STL的容器要装元素,必须要有空间,而空间从哪里来,从空间配置器allocator中来。

为了减少内存碎片的产生以及提高效率,STL采用两级的空间配置器。有图有真相:

(原创滴~)STL源码剖析读书总结1——GP和内存管理

其中,如果需求区块大于128 bytes,则用的是第一级配置器;否则使用第二级配置器,STL一般都默认使用第二级配置器。

先讲第一级配置器:

STL中分配内存和初始化对象时分开进行的。对象的构造construct()比较简单,直接用placement new(用于在已经分配好的空间中构造对象),调用该类型的构造函数就好了。而析构destroy()因为涉及到该类型是不是含trivial destructor,因而有些小复杂。所谓trivial destructor就是那些无关痛痒的dtor(例如,函数体为空),当我们知道含有trivial dtor,我们在析构的时候就可以不调用那些trivial dtors(因为无关痛痒嘛~),从而降低函数调用的成本。相反,若不含trivial dtors(例如涉及到new-delete的动态内存分配),我们在析构的时候就要老老实实调用dtor,否则会memory leak哦~废话少说,上图:

(原创滴~)STL源码剖析读书总结1——GP和内存管理

第一级配置器用到了一个内存不足处理例程,即当遇到内存不足,无法分配的时候,就调用这个处理例程。这个处理例程可能会释放一些内存,然后继续尝试分配。这个类似于C++的new-handler机制。

然后是第二级配置器。第二级配置器采用一个freelists的*链表来管理空闲的区块,有16条不同大小的区块链表,大小从8,16,24...到128 bytes。目的是避免太多的小区快造成内存碎片,同时减少配置时的overhead。例如cookie(你在new一个对象的空间时,系统可能会分配比sizeof(你的对象)略大的内存空间,多出了空间用来记录内存大小,这样delete []p的时候就知道该free多少内存空间了~)。使用第二级配置器分配空间时,如果能从freelists中找到满足需求的最小区块,则从freelists中拨一个区块出去,在deallocate的时候再回收这个区块。当freelists中所需大小的区块已经一个不剩了(该大小的区块链表为null),则调用refill,从memory pool(内存池)中获取一整块大内存,再切分为所需大小的区块,链接到该链表,为该大小的区块“补仓”。如果memory pool也“没货”了,则唯有向heap要了。如果heap有,则向memory pool“注水”;如果heap也没有,则会调用第一级配置器,那里有个内存不足处理例程,实在不行会抛出个exception什么的。再见图:

(原创滴~)STL源码剖析读书总结1——GP和内存管理

另外,除了construct()和destroy()以外,还有三个有用的作用于未初始化内存的函数:

(原创滴~)STL源码剖析读书总结1——GP和内存管理

好了,今天先讲这么多,碎觉去~~~

上一篇:通读《STL源码剖析》之后的一点读书笔记


下一篇:AndroidManifest.xml的targetSdkVersion 与 project.properties中target