书接上回,我们做了个单模类,里面提供两个重要的函数Alloc和Free供内存申请和释放时调用。接着我们可以overload operator new和delete函数,在里面分别调用Alloc和Free
void *operator new (size_t nSize, int nLine, const char* szFile)
{
return MemPool::GetInstance().Alloc((int) nSize, szFile, nLine, false);
}
void operator delete(void *p)
{
MemPool::GetInstance().Free(p);
}
new函数可以各种overload,具体可以google一下。这里的overload,除了第一个size_t的尺寸参数外,后面两个参数对应你源文件里的行数和文件名。这个是用来定位内存泄露的,后面会讲。用的时候,申请代码的那行这么写
whatever *p = new(__LINE__, FILE_NAME) whatever
其中FILE_NAME是个宏,表示当前的源文件名(去掉了目录的)
#define FILE_NAME ((strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') : __FILE__ - 1) + 1)
上面的__LINE__
和__FILE__
是C的内置宏,编译器编译的时候会填入对应的数据。delete的使用方式就不用讲了吧。下面是数组的分配释放函数
void *operator new[] (size_t nSize, int nLine, const char* szFile)
{
return MemPool::GetInstance().Alloc((int) nSize, szFile, nLine, true);
}
void operator delete[](void *p)
{
MemPool::GetInstance().Free(p);
}
和前面的operator new、operator delete的有啥区别?注意那个调用的Alloc的最后的参数,一个是false一个是true。翻翻前面的Alloc实现里面对这个参数做了啥?啥都没有啊!包括行号,文件名都没做任何操作。好吧,这个后面会加,泄漏和越界调试用的,现在先放一边吧。
下面说说清理函数了。我们知道,刚刚实现完毕的内存池(那个单模类)就是个静态类,程序启动时进行的构造和初始化,程序结束时进行析构。那么问题来了,假如我还有一个静态类,这个类能用这个内存池申请和释放它需要的内存么?比如下面这样
class test
{
private:
char* m_pArray;
static test m_instance;
test() {
m_pArray = NULL;
};
public:
~test() {
if (m_pArray)
delete[] m_pArray;
};
void Init() {
m_pArray = new(__LINE__, FILE_NAME)[100];
...
};
static test& GetInstance() { return m_instance; };
};
test test::m_instance; // 单模类实例
void main()
{
...
test::GetInstance().Init();
...
}
问题很大。因为静态变量的构造和析构的次序是不定,除非都在一个源文件里。你不能保证内存池的销毁一定在其他静态类的后面。如果内存池已经销毁了,然后某个静态类在他的析构函数里释放了一个它从内存池申请的内存块,那就崩了。
怎么办?把上面这个类改一下
class test
{
...
public:
~test() {
Clear();
};
void Clear() { // 释放相关内存
if (m_pArray)
delete[] m_pArray;
m_pArray = NULL;
};
static void SomeCleanup(void *pParam) { // 清理函数
((test*) pParam)->Clear();
};
void Init() {
MemPool::GetInstance().AddCleanup(SomeCleanup, this); // 注册清理函数
m_pArray = new(__LINE__, FILE_NAME)[100];
...
};
...
};
注册清理函数的部分如下
void MemPool::AddCleanup(CleanupHandler pHandler, void* pParam)
{
Cleanup *p = new Cleanup();
p->pHandler = pHandler; p->pParam = pParam;
p->pNext = m_pCleanups;
m_pCleanups = p;
}
OK,现在内存池的实现基本完成了。