如果之前学过操作系统的内存管理机制和内存分配算法等知识,那么就了解“内存池”的概念。
简单地说,内存池预先分配了一块大的内存空间,然后就可以在其中使用某种算法实现高效快速的自定制内存分配。
boost.pool库基于简单分配存储思想实现了一个快速、紧凑的内存池库,不仅能够管理大量的对象,而且还可以被用作STL的内存分配器。它近似于一个小型的垃圾回收机制,在需要大量地分配/释放小对象时很有效,并且完全不需要考虑delete。
pool库包含四个部分,最简单地pool、分配类实例的object_pool、单件内存池singleton_pool和可用于标准库的pool_alloc。
1:pool
pool是最简单也最容易使用的内存池类,可以返回一个简单数据类型(POD,Plain Old Data)的内存指针,它位于命名空间boost,为了使用pool组件,需要包含头文件
类摘要:
template
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
#include
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
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
#include
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 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 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
#include
#include
using namespace std;
using namespace boost;
int main(){
//使用pool_allocator代替标准容器默认的内存分配器
vector > v;
//vector将使用新的分配器良好工作
v.push_back(10);
cout << v.size() << endl;
}