前言
我们每一次使用new来给类对象分配内存的时候都需要在所分配空间的头尾加上两个cookie来保存所分配内存的大小,这就增加了八个字节的开销。所以想要减小这个开销的办法就是一次性开辟一个大的内存,将这个大的内存划分成固定大小的块,块与块之间用指针连接(形成了一个链表),每次new的时候从这个内存里返回一块空间,delete的时候再将这块内存放回大的内存中去。这样的方式就形成了一个内存池。根据之前文章提到的内容,我们可以通过重载operator new 和 operator delete 的方式来实现内存池。
正文
1.成员指针实现内存池
class A
{
private:
A * next; //指针
static A* freeStore; //静态指针存放内存池的首地址
static const int Chunk; //每次申请的块数
public:
int i;
A(int i):i(i){}
void* operator new(size_t size)
{
A* p;
if(!freeStore) //内存池中没有剩余空间,重新申请
{
size_t chunk = Chunk*size;
freeStore = p = (A*)(new char[chunk]);
for(;p!=&freeStore[Chunk-1];p++)
{
p->next = p+1;
}
p->next = NULL;
}
p = freeStore;
freeStore = freeStore->next;
return p;
}
void operator delete(void *p,size_t size)
{
(A*)p->next = freeStore;
freeStroe = (A*)p;
}
};
A* A::freeStore = NULL;
const int Chunk = 24;
这种方式实现的内存池,会在类的内部放一个成员指针,这造成了每个该类对象都需要给这个指针分配空间,然而一旦内存被分配结束,指针就是去了作用,白白浪费掉了四个字节。所以我们采用嵌入式指针的方式来实现块与块之间的连接。
2.嵌入式指针实现内存池
class A
{
private:
struct obj
{
A* next;
};
static A* freeStore; //静态指针存放内存池的首地址
static const int Chunk; //每次申请的块数
public:
int i;
A(int i):i(i){}
void* operator new(size_t size)
{
A* p;
if(!freeStore)
{
size_t chunk = Chunk * size;
freeStore = p = (A*)(new char[chunk]);
for(;p!=freeStore[Chunk-1];p++)
{
((obj*)p)->next = p+1;
}
((obj*)p)->next = NULL;
}
p = freeStore;
freeStore = ((obj*)freeStore)->next;
return p;
}
void operator delete(void *p,size_t size)
{
(obj*)p->next = freeStore;
freeStore = (A*)p;
}
};
A* A::freeStore = NULL;
const int Chunk = 24;
我们将每个内存块的前四个字节看成一个指针,在内存池中的块,用这个指针来连接下一块内存;一旦被拿出了内存池,前四个字节就作为类成员的地址空间正常使用。这样的嵌入式指针既能实现作用,又不额外占用空间。但是使用这种方式的前提就是类的大小必须大于等于四字节。
3.静态的allocator
class allocator
{
private:
struct obj
{
obj* next;
};
obj * freeStore = NULL;
const int Chunk = 24;
public:
void * allocate(size_t size)
{
obj* p;
if(!freeStore)
{
size_t chunk = Chunk *size;
freeStore = p = (obj*)(new char[chunk]);
for(int i=0;i<Chunk-1;i++)
{
p->next = (obj*)((char*)p+size);
}
p->next = NULL;
}
p = freeStore;
freeStore = freeStore->next;
return p;
}
void deallocate(void *p,size_t size)
{
((obj*)p)->next = freeStore;
freeStore = (obj*)p;
}
};
class A
{
private:
static allocator myAlloc;
public:
int i;
A(int i):i(i){}
void* operator new(size_t size)
{
return myAlloc.allocate(size);
}
void* operator delete(void *p,size_t size)
{
myAlloc.deallocate(p,size);
}
};
allocator A::myAlloc;
为了方便在不同类中实现内存池,我们封装了一个allocator类用于内存分配。在自定义类的内部设置allocator的静态变量,通过重载operator new 和 operator delete 来调用静态allocator变量中的allocate 和 deallocate接口实现上述功能。
为了更方便使用,可以使用宏定义来实现静态变量定义和接口调用。
//静态变量定义和接口调用的宏
#define DECLEAR_POOL_ALLOC()\
public:\
void* operator new(size_t size){return myAlloc.allocate(size);}\
void operator delete(void*p,size_t size){myAlloc.deallcoate(p,size);}\
protected:\
static allocator myAlloc;
//静态变量初始化的宏
#define IMPLEMENT_POOL_ALLOC(class_name)\
allocator class_name::myAlloc;
class A
{
public:
int i;
A(int i) :i(i){}
DECLEAR_POOL_ALLOC()
};
IMPLEMENT_POOL_ALLOC(A)
这样就完成了一个类内部内存分配器的设计。