内存分配(器)返回的是一块没有初始化的内存,不返回对象;内存分配(器)也不支持设置成虚指针,因为内存分配是在构造执行后;
1 void* raw = allocate(sizeof(Foo)); // line 1
2 Foo* p = new(raw) Foo(); // line 2
allocate就是一个简单的内存分配(器),上面的代码使用了placement new来构造对象。
下面一步就是做一个对象的内存分配(器)-------------内存池(类)
1 class Pool {
2 public:
3 void* alloc(size_t nbytes);
4 void dealloc(void* p);
5 private:
6
data members used in your pool object
7 };
8
9 void* Pool::alloc(size_t nbytes)
10 {
11
your algorithm goes here
12 }
13
14 void Pool::dealloc(void* p)
15 {
16
your algorithm goes here
17 }
分配对象使用下面的代码
1 Pool pool;
2
3 void* raw = pool.alloc(sizeof(Foo));
4 Foo* p = new(raw) Foo();
或者更简单的代码
Foo* p = new(pool.alloc(sizeof(Foo))) Foo();
这样做的好处是可以使用N个内存池而不是共享一块大内存,可以从内存池中分配而不用释放,一次释放整个内存池;也可以其中一部分做共享内存而不用系统的共享内存,从另一个角度来看,很多系统支持alloca()从stack中分配内存块,当然这些内存块也会自动离开在函数返回时,无须delete;当然也可以使用alloca()为(内存)池分配一个大内存块,而无须调用delete,在这里析构函数仅仅只是(deallocates)释放分配的内存。
下一步就是就是改变分配对象的语法:与其使用new(pool.alloc(sizeof(Foo)))
Foo() 倒不如new(pool)
Foo(),为此我们需要实现下面的函数:
inline void* operator new(size_t nbytes, Pool& pool)
{
return pool.alloc(nbytes);
}
现在看看这个new(pool)Foo(),它会调用operator new,通过sizeof(Foo)和pool两个参量,下面看看如何析构对象和释放内存,根据placement new的析构方法,代码如下:
void sample(Pool& pool)
{
Foo* p = new(pool) Foo();
p->~Foo(); // explicitly call dtor
pool.dealloc(p); // explicitly release the memory
}
但这样做,会有下面几个问题产生:
问题一:防止内存泄露,在Foo* p = new Foo(),编译器会产生类似下面的代码来应对构造中的异常
// This is functionally what happens with Foo* p = new Foo()
Foo* p;
// don't catch exceptions thrown by the allocator itself
void* raw = operator new(sizeof(Foo));
// catch any exceptions thrown by the ctor
try {
p = new(raw) Foo(); // call the ctor with raw as this
}
catch (
) {
// oops, ctor threw an exception
operator delete(raw);
throw; // rethrow the ctor's exception
}
问题是在deallocates(去分配)内存时如发生异常,具体的说就是new的参数(调用placement(布局) new)时出现异常,此时编译器不知所措,默认就什么都不做。
// This is functionally what happens with Foo* p = new(pool) Foo():
void* raw = operator new(sizeof(Foo), pool);
// the above function simply returns "pool.alloc(sizeof(Foo))"
Foo* p = new(raw) Foo();
// if the above line "throws", pool.dealloc(raw) is NOT called
所以编译器要做一些类似于全局new operator的工作,当编译器看到
new(pool) Foo(),它要查找与之想匹配的delete operator,如果找到了,就相当于上面显示
的包含try的代码可以正确执行,所以要写个delete operator,像下面的代码所示(即使第二参数和operator new(size_t, Pool&)中的第二个参数不同,也没有关系,编译器也
不会有什么抱怨,此时它会绕过这个try块)
void operator delete(void* p, Pool& pool)
{
pool.dealloc(p);
}
注意:这个operator delete不必一定是inline的,可以是,也可以不是。
destruction(析构)/deallocation(去分配)是两个不同的概念,可现实中经常混淆这两个;另外要记住内存池里的内存和哪个对象匹配,一般我们通过Foo *和
pool* 这两个指针来定位;对于这两个方面可能出现的问题,可以用下面的方案来解决。
这个方案就是使每次分配(对象)都关联一个pool *,然后在替代全局delete opeator,如下所示:
void operator delete(void* p)
{
if (p != NULL) {
Pool* pool = /* somehow get the associated 'Pool*' */;
if (pool == null)
free(p);
else
void* operator new(size_t nbytes)
{
if (nbytes == 0)
nbytes = 1; // so all alloc's get a distinct address
void* raw = malloc(nbytes);
somehow associate the NULL 'Pool*' with 'raw'
return raw;
}
pool->dealloc(p);
}
如果不确信dealloc调用free,可以在全局的new operator中使用malloc(),像下面的代码所示(此短代码没有throw std::bad_alloc()new_handler)
下面的工作就是用pool*关联每次 allocation(分配),这里我们可以使用STL中的std::map<void*,pool*>,即建立查找的查询表,其keys是分配指针(void*),value是
pool*,这里要注意,在从全局的operator new向map中添加数据,因为如果是NULL,会导致递归(结束一次插入同时又插入新项)。
这里用map这个STL里的容器,在大多数情况是可以接受的。
除了map,还有一种方案就是耗内存但是速度快些。就在在pool*之前加上所有的allocate(分配),如果字节是24,考虑到边界对齐(double\long long),可能需要28或32字节,首先给pool*四个字节,然后返回4或8个字节的(指针),从你开始分配时;此时全局的delete opeator至少可以删除这4或8字节指针而不会出现异常,这是如果pool*是null,那么用free调用pool->dealloc(),由free和pool->dealloc()会有4或8字节的指针到原参p,如果不为空,你就可以决定4字节对齐,代码就要像下面示例的这样:
void* operator new(size_t nbytes)
{
if (nbytes == 0)
nbytes = 1; // so all alloc's get a distinct address
void* ans = malloc(nbytes + 4); // overallocate by 4 bytes
*(Pool**)ans = NULL; // use NULL in the global new
return (char*)ans + 4; // don't let users see the Pool*
}
void* operator new(size_t nbytes, Pool& pool)
{
if (nbytes == 0)
nbytes = 1; // so all alloc's get a distinct address
void* ans = pool.alloc(nbytes + 4); // overallocate by 4 bytes
*(Pool**)ans = &pool; // put the Pool* here
return (char*)ans + 4; // don't let users see the Pool*
}
void operator delete(void* p)
{
if (p != NULL) {
p = (char*)p - 4; // back off to the Pool*
Pool* pool = *(Pool**)p;
if (pool == null)
free(p); // note: 4 bytes left of the original p
else
pool->dealloc(p); // note: 4 bytes left of the original p
}
}
上面的代码并没有处理new operator内存不足的情况,如果我们不改变全局的new operator和delete operator,上面的问题依然存在。