先说想法
我想尝试写一种对象池模型,使得所有经有new得到的对象实际上都会从一个链表中取,并在delete的时候把资源返回给链表。
完整源码
我写了一个demo来实现我的思路,思路不复杂,但想尽量写的精致。也希望有想法的看官能提点建议。
源码见:https://github.com/HandsomeRosin/ObjectPool
顺便在这里说明下,目前demo没实现new[]和delete[]的重载,所以该对象池模型只适用于单个对象的动态分配。
代码示例
RSHelper便是我们的对象池基类,以下为测试demo:
#include "RSHelper.h"
#include <iostream>
using namespace std;
class handsome : public RSHelper<handsome, 8> { // 这里默认对象池中的对象数是8
public:
handsome(int a = 0) {
i = a;
}
int i;
};
int main() {
handsome* hs[13];
for (int i = 0; i < 13; ++i) {
hs[i] = new handsome(i); // 有8个从对象池中获得,5个经由malloc获得。
}
cout << "----------------------------------------------------------" << endl;
for (int i = 12; i >= 0; --i) {
delete hs[i]; // 一致的回收方式,内部会自动识别是否是对象池中的对象,如果不是则会释放对象的内存。
}
}
大致思路
首先不可避免的,这种模型必须要重载new和delete,为了实现解耦,我决定写一个基类,让这个基类重载new和delete。并且为了指定对象池的对象类型和内存池大小,这个基类需要有2个模板参数。如此,这个基类的大致结构便定下来了。
此外,我希望在对象池不够时能调用malloc动态分配内存,并在delete时能自动识别该对象是经由malloc得来的,抑或是对象池中的,若是前者,则还需要调用free。
以下是该基类:
#pragma once
#include "ConsecutiveList.h"
template<typename T, int S = 1024> // S是对象池中的对象数目
class RSHelper {
private:
class RSManager {
private:
ConsecutiveList list; // 自定义的链表对象,用于管理所有的空闲对象的内存地址指针,是线程安全的。
public:
RSManager(): list(S, sizeof(T)) { }
// 得到一个指向可用于初始化对象的内存的指针
void* get(size_t size) {
void* temp = list.get();
if (temp == NULL) {
return malloc(size);
}
return temp;
}
// 回收一个由get得到的指针,有可能放回链表,有可能free
void put(void* t) {
list.put(t);
}
};
static RSManager manager_;
public:
void* operator new(size_t sz) {
return manager_.get(sz);
}
void operator delete(void* m) {
if (!m)
return;
manager_.put(m);
}
};
template<typename T, int S>
typename RSHelper<T, S>::RSManager RSHelper<T, S>::manager_;
对象链表为了达到优化效果,这里采用了内存池的方式,即直接分配一大块内存,在这块内存上构造多个对象,并构建一个指针链表指向这块内存中的所有空闲块(每个空闲块可用于构造一个对象)。这样的内存池结构也可以保证在delete时能通过地址大小判断要回收的地址空间是内存池中的地址还是经由malloc得到的地址。
class ConsecutiveList {
private:
void* ptr; // 指向一块连续的内存
void* upper_bound_address; // ptr所指的连续内存的上界
// 无锁栈(采用链表结构)
class LockFreeList {
// 。。。
} list; // 用于存放指针链表,链表中的指针指向连续内存中的每个对象起始地址
// 为了保证无论什么情况下指向堆对象的指针最后都能调用delete
template<class T>
struct DeleteGuard {
// 。。。
};
const size_t el_size; // 对象大小
const size_t el_num; // 内存中可存的对象数
public:
ConsecutiveList(size_t _el_num, size_t _el_size):
el_num(_el_num),
el_size(_el_size),
list()
{
size_t total_size = el_num * el_size;
ptr = malloc(total_size);
upper_bound_address = (char*)ptr + total_size;
int loop = el_num - 1;
char* p = (char*)ptr;
do {
list.push(p);
p += el_size;
} while (loop--);
}
void* get() {
if (list.empty()) {
return NULL;
}
void* temp = list.pop();
return temp;
}
void put(void* p) {
if (p >= ((void*)ptr) && p < upper_bound_address) {
list.push(p);
return;
}
free(p);
}
~ConsecutiveList() {
free(ptr);
}
};
这个对象链表的真正数据结构在LockFreeList上,顾名思义,为了实现线程安全,这里采用了原子操作,这里的LockFreeList实际上是无锁栈。
class LockFreeList {
private:
struct Node {
Node* next;
void* ptr;
};
std::atomic<Node*> head;
public:
bool empty() {
return head.load() == NULL;
}
void push(void* p) {
DeleteGuard<Node> node(new Node); // 可将DeleteGuard类比为智能指针
node->ptr = p;
do {
node->next = head.load();
} while (!head.compare_exchange_weak(node->next, node.obj));
node.obj = NULL;
}
void* ConsecutiveList::LockFreeList::pop() {
DeleteGuard<Node> node; // 可将DeleteGuard类比为智能指针
do {
node = head.load();
if (node.isNULL()) {
return NULL;
}
} while (!head.compare_exchange_weak(node.obj, node->next));
return node->ptr;
}
};
上面的DeleteGuard是为了防止push和pop中途报错导致堆对象无法得到释放而导致内存泄漏。我还重载了赋值运算符和箭头运算符来方便操作。本来还想重载强转运算符的,但想到《More Effective C++》中所说的“尽量别重载强转运算符”,就算了。
template<class T>
struct DeleteGuard {
T* obj;
DeleteGuard(T* ptr = NULL) : obj(ptr) { }
~DeleteGuard() {
if (obj != NULL) {
delete obj;
}
}
T* operator->() {
return obj;
}
void operator=(T* ptr) {
obj = ptr;
}
bool isNULL() {
return obj == NULL;
}
};