如果之前学过操作系统的内存管理机制和内存分配算法等知识,那么就了解“内存池”的概念。
简单地说,内存池预先分配了一块大的内存空间,然后就可以在其中使用某种算法实现高效快速的自定制内存分配。
boost.pool库基于简单分配存储思想实现了一个快速、紧凑的内存池库,不仅能够管理大量的对象,而且还可以被用作STL的内存分配器。它近似于一个小型的垃圾回收机制,在需要大量地分配/释放小对象时很有效,并且完全不需要考虑delete。
pool库包含四个部分,最简单地pool、分配类实例的object_pool、单件内存池singleton_pool和可用于标准库的pool_alloc。
1:pool
pool是最简单也最容易使用的内存池类,可以返回一个简单数据类型(POD,Plain Old Data)的内存指针,它位于命名空间boost,为了使用pool组件,需要包含头文件<boost/pool/pool.hpp>。
类摘要:
template<typename UserAllocator > class pool{ public: explicit pool(size_type requested_size); ~pool(); size_type get_requested_size() const; void *malloc(); void *ordered_malloc(); void *ordered_malloc(size_type n); bool is_from(void *chunk) const; void free(void *chunk); void ordered_free(void *chunk); void free(void *chunks,size_type n); void ordered_free(void *chunks,size_type n); bool release_memory(); bool purge_memory(); };
操作函数
pool的模板类型参数UserAllocator 是一个用户定义的内存分配器,它实现了特定的内存分配算法,通常可以直接用默认的default_user_allocator_new_delete。
pool的构造函数接受一个size_type类型的整数requested_size,指示每次pool分配内存块的大小(而不是pool内存池的大小),这个值可以用get_requested_size()获得。pool会根据需要自动地向系统申请或归还使用的内存,在析构时,pool将自动释放它所持有的所有内存块。
成员函数malloc()和ordered_malloc()的行为很类似C中的全局函数malloc(),用void*指针返回从内存池中分配的内存块,大小为构造函数中指定的requested_size。如果内存分配失败,函数将返回0,不会抛出异常。malloc()从内存池中任意分配一个内存块,而ordered_malloc()则在分配的同时合并空闲块链表。ordered_malloc()带参数的形式还可以连续分配n块的内存。分配后的内存块可以用is_from()函数测试是否是从这个内存池分配过去的。
与malloc()对应的一组函数是free(),用来手工释放之前分配的内存块,这些内存块必须是从这个内存池分配出去的(is_from(chunk) == true)。一般情况内存池会自动管理内存分配,不应该调用free()函数,除非你认为内存池的空间已经不足,必须释放已经分配的内存。
最后还有两个成员函数:release_memory()让内存池释放所有未被分配的内存,但已分配的内存块不受影响;purge_memory()则强制释放pool所持有的所有内存,不管内存块是否被使用。实际上,pool的析构函数就是调用的purge_memory()。这两个函数一般情况下也不应该手工调用。
使用示例:
#include <iostream> #include <boost/pool/pool.hpp> using namespace boost; using namespace std; int main(){ //一个可分配int的内存池 pool<> pl(sizeof(int)); //必须把void*转换成需要的类型 int *p = (int *)pl.malloc(); assert(pl.is_from(p)); cout << "pl is allocated from pool." << endl; //释放内存池分配的内存块 pl.free(p); cout << "Free memory pool of the allocated memory block" << endl; //连续分配大量的内存 for(int i = 0; i < 100; ++i) pl.ordered_malloc(10); cout << "allocated a large of memory" << endl << "now free all memory..." << endl; }
运行结果:
pl is allocated from pool.
Free memory pool of the allocated memory block
allocated a large of memory
now free all memory...
pool很容易使用,可以像C中的malloc()一样分配内存,然后随意使用。除非有特殊要求,否则不必对分配的内存调用free()释放,pool会很好地管理内存。
因为pool在分配内存失败的时候不会抛出异常,所以实际编写代码时应该检查malloc()函数返回的指针,以防止空指针错误。不过这种情况极少出现。
int *p = (int *)pl,malloc();
if(p != NULL);
关于pool<>需要注意的是:它只能作为普通数据类型如int、double、float等的内存池,不能应用于复杂的类和对象,因为它只负责分配内存,不调用构造函数。
object_pool
object_pool是用于类对象的内存池,它的功能与pool类似,但会在析构时对所有已经分配的内存块调用析构函数,从而正确地释放资源。
类摘要:
template<typename ElementType> class object_pool:protected pool{ public: object_pool(); ~object_pool(); element_type *malloc(); void free(element_type *p); bool is_from(element_type *p) const; element_type * construct(); void destory(element_type *p); };
操作函数
object_pool是pool的子类,但它使用的是保护继承,因此不能使用pool的接口。
object_pool的模板参数类型参数ElementType指定了object_pool 要分配的元素类型,要求其析构函数不能抛出异常。一旦在模板中指定了类型,object_pool对象就不能再用于分配其他类型的对象。
malloc()和free()函数分别分配和释放一块类型为ElementType*的内存块,同样,可以用is_from()来测试内存块的归属,只有是本内存池分配的内存才能被free()函数释放掉。但它们被调用时并不调用类的构造函数和析构函数,也就是说操作的是一块原始内存块,里面的值是未定义的。因此应该尽量少用malloc()和free()。
object_pool的特殊之处是construct()和destory()函数,这两个函数是object_pool的真正价值之所在。construct()实际上是一组函数,有多个参数的重载形式(目前最多支持三个参数,但可以扩展),它先调用malloc()分配内存,然后再在内存块上使用传入的参数调用类的构造函数,返回的是一个已经初始化的对象指针。destory()则先调用对象的析构函数,然后再用free()释放内存块。
使用示例:
#include <iostream> #include <boost/pool/object_pool.hpp> using namespace std; using namespace boost; //一个示范类 struct demo_class{ public: int a,b,c; demo_class(int x = 1,int y = 2,int z = 3):a(x),b(y),c(z){} }; int main(){ //对象内存池 object_pool<demo_class> pl; //分配一个原始的内存块 demo_class *p = pl.malloc(); assert(pl.is_from(p)); cout << "ok,allocated from pl..." << endl; //p指向的内存未经过初始化 assert(p->a != 1 || p->b != 2 || p->c != 3); //构造一个对象,可以传递参数 p = pl.construct(7,8,9); assert(p->a == 7); cout << p->a << " " << p->b << " " << p->c << endl; //定义一个分配string对象的内存池 object_pool<string> pls; //连续分配大量string对象 for(int i = 0; i < 10; ++i){ string *ps = pls.construct("hello object_pool"); cout << *ps << endl; } //所有创建的对象在这里都被正确地析构、释放内存 }
singleton_pool
singleton_pool与pool的接口完全一致,可以分配简单数据类型的内存指针,但它是一个单件,并提供线程安全。
由于目前Boost还未提供标准的单件库,singleton_pool在其内部实现了一个较简单、泛型的单件类,保证在main()函数运行之前就创建单件。
类摘要:
template< typename Tag,unsigned RequestedSize > class singleton_pool{ public: static bool is_from(void *ptr); static void *malloc(); static void *ordered_malloc(); static void *ordered_malloc(size_type n); static void free(void *ptr); static void ordered_free(void *ptr); static void free(void *ptr,std::size_type n); static bool release_memory(); static bool purge_memory(); };
singleton_pool主要有两个模板类型参数(其余的可以使用缺省值)。第一个Tag仅仅是用于标记不同的单件,可以是空类,甚至是声明(这个用法还被用于boost.exception)。
第二个参数RequestedSize等同于pool构造函数中的整数requested_size,指示pool分配内存块的大小。
singleton_pool的接口与pool完全一致,但成员均是静态的,因此不需要声明singleton_pool的对象,直接用域操作符::来调用静态成员函数。因此singleton_pool是单件,所以它的生命周期与整个程序一样长,除非搬运调用release_memory()或者purge_memory(),否则singleton_pool不会自动释放所占用的内存。除了这两点其他用法与pool完全相同。
pool_alloc
pool_alloc提供了两个可以用于标准容器模板参数的内存分配器,分别是pool_alloc和fast_pool_allocator,它们的行为与之前的内存池类有一点不同-----当内存分配失败时会抛出异常std::bad_alloc,除非特别的需求,我们应该总使用STL实现自带的内存分配器,使用pool_alloc需要经过仔细测试,以保证它与容器可以共同工作。
使用示例:
#include <iostream> #include <vector> #include <boost/pool/pool_alloc.hpp> using namespace std; using namespace boost; int main(){ //使用pool_allocator代替标准容器默认的内存分配器 vector<int,pool_allocator<int> > v; //vector将使用新的分配器良好工作 v.push_back(10); cout << v.size() << endl; }