object_pool是用于类实例(对象)的内存池,它的功能与pool类似,但会在析构时对所有已经分配的内存块调用析构函数,从而正确地释放资源。
object_pool位于名字空间boost,为了使用object_pool组件,需要包含头文件
,即:
#include
using namespace boost;
template <typename T, typename UserAllocator>
class object_pool : protected pool<UserAllocator>
{
public:
typedef T element_type;
public:
object_pool(); //构造函数
~object_pool(); //析构函数
element_type * malloc(); //分配内存
void free(element_type * p); //归还内存
bool is_from(element_type * p) const;
element_type * construct(...); //创建对象
void destroy(element_type * p); //销毁对象
};
object_pool是pool的子类,但它使用的是保护继承,因此不能使用pool的接口,但基本操作还是很相似的。
object_pool 的模板类型参数T指定了object_pool要分配的元素类型,要求其析构函数不能抛出异常。一旦在模板中指定了类型,object_pool实例就不能再用于分配其他类型的对象。
malloc()和 free()函数分别分配和释放一块类型为T*的内存块,同样,可以用is_from()来测试内存块的归属,只有是本内存池分配的内存才能被free()释放。但它们被调用时并不调用类的构造函数和析构函数,也就是说,操作的是一块原始内存块,里面的值是未定义的,因此我们应当尽量少使用malloc()和 free()。
object_pool 的特殊之处是 construct()和destroy()函数,这两个函数是object_pool的真正价值所在。construct()实际上是一组函数,有多个参数的重载形式(目前最多支持3个参数,但可以扩展),它先调用malloc()分配内存,然后再在内存块上使用传入的参数调用类的构造函数,返回的是一个已经初始化的对象指针。destory()则先调用对象的析构函数,然后再用free()释放内存块。
这些函数都不会抛出异常,如果内存分配失败,将返回0。
object_pool的用法也很简单,我们既可以像pool那样分配原始内存块,也可以使用construct()来直接在内存池中创建对象。当然,后一种使用方法是最方便的。
#include
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; //对象内存池
auto p = pl.malloc(); //分配一个原始内存块
assert(pl.is_from(p));
assert(p->a != 1 || p->b != 2 || p->c != 3);
p = pl.construct(7, 8, 9); //构造一个对象,可以传递参数
assert(p->a == 7);
object_pool<string> pls; //定义一个分配string对象的内存池
for (int i = 0; i < 10; ++i) //连续分配大量string对象
{
string* ps = pls.construct("hello object_pool");
cout << *ps << endl;
}
} //所有创建的对象在这里都被正确析构、释放内存
默认情况下,在使用object_pool的construct()的时候我们只能最多使用3个参数来创建对象。大多数情况下这都是足够的,但有的时候我们可能会定义3个以上参数的构造函数,此时construct()的默认重载形式就不能用了。
很遗憾,construct()并没有及时跟进C++11标准使用可变参数模板支持任意数量的参数构造。它基于宏预处理m4(通常UNIX系统自带,也有windows的版本)实现了一个变通的扩展机制,可以生成接受任意数量参数的construct()函数代码。
pool库在目录/boost/pool/detail下提供了一个名为 pool_construct.m4 和pool_construct_simple.m4 的脚本,并同时提供可在UNIX和 windows下运行的同名sh和 bat可执行脚本文件。只需要简单地向批处理脚本传递一个整数的参数N,m4就会自动生成能够创建具有N个参数的construct()函数源代码。
例如,在 Linux 下,执行命令:
./pool_construct_simple.sh 5; ./ pool_construct.sh 5
将生成两个同名的.ipp文件,里面包含了新的construct()函数定义,能够支持最多传递5个参数创建对象。
m4 的解决方案显得比较笨拙,使用c++11的可变参数模板特性,我们可以定义一个辅助模板函数,支持任意数量的参数,彻底解决这个问题:
template<typename P, typename ... Args> //C++11可变参数模板
inline typename P::element_type*
construct(P& p, Args&& ... args)
{
typename P::element_type* mem = p.malloc();
assert(mem != 0);
new (mem) typename P::element_type(
std::forward<Args>(args)...); //C++11的完美转发
return mem;
}
自由函数construct()接受任意多个参数,第一个是object_pool对象,其后是创建对象所需参数,要创建的对象类型可以使用object_pool 的内部类型定义element_type来获得。函数中首先调用malloc()分配一块内存,然后调用不太常见的“定位new表达式”(placement new expression)创建对象。
假设我们有如下的一个4参数构造函数的类:
struct demo_class
{
demo_class2(int, int, int, int) //构造函数接受4个参数
{
cout << "demo_class ctor" << endl;
}
~demo_class2()
{
cout << "demo_class dtor" << endl;
}
};
那么使用自定义的construct()创建对象的代码就是:
object_pool<demo_class> p1;
auto d = construct(p1,1,2,3,4);
#include
using namespace std;
#include
//#define BOOST_SYSTEM_NO_DEPRECATED
//#define BOOST_POOL_NO_MT
#include
using namespace boost;
#include
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) {}
};
void case2()
{
object_pool<demo_class> pl;
auto p = pl.malloc();
assert(pl.is_from(p));
assert(p->a != 1 || p->b != 2 || p->c != 3);
p = pl.construct(7, 8, 9);
assert(p->a == 7);
object_pool<string> pls;
for (int i = 0; i < 10; ++i)
{
string* ps = pls.construct("hello object_pool");
cout << *ps << endl;
}
}
//
template<typename P, typename ... Args>
inline typename P::element_type*
construct(P& p, Args&& ... args)
{
typename P::element_type* mem = p.malloc();
assert(mem != 0);
new (mem) typename P::element_type(
std::forward<Args>(args)...);
return mem;
}
struct demo_class2
{
demo_class2(int, int, int, int)
{
cout << "demo_class ctor" << endl;
}
~demo_class2()
{
cout << "demo_class dtor" << endl;
}
};
void case3()
{
object_pool<demo_class2> pl;
auto d = construct(pl, 1, 2, 3, 4);
boost::ignore_unused(d);
}
int main()
{
case2();
case3();
}