在游戏编程时,每个关卡都要创建大量的对象,例如NPC,树,草,地形等,而有些对象需要有游戏中实时创建,比如爆炸效果,子弹。给游戏对象分配内存本来就是比较耗费时间的,如果采用传统的new 和delete的话,可能会使得游戏的实时性降低。因此可以采用对象池的方法。即先根据关卡估计会用到对象数量,在关卡开始时先分配好需要的对象数量缓存起来,以后直接使用这些缓存的对象就OK了,当游戏关卡结束时,清除对象池。
本文采用了模版方法,参照了《面向对象游戏开发--julian Gold》第七章的内容。对于一种对象的缓存池类来说,它在程序中只应该有一个对象。因为为一种对象创建两个或多个缓存池根本就没有必要,反而会增加代码的复杂度。因此我采用了单件模式。
那么怎样将对象缓存起来呢,我们只需要为每个类重载操作符 new 和 delete 即可,当用户new 一个对象时,我们从缓存池里拿出一个事先分配好的对象,返回给用户即可。当用户调用 delete时,我们将请求删除的对象放入缓存池,方便下次用户调用new时使用。
内存池代码如下:
//用法示例
/*
class A
{
public:
NEWCLASS(A);
DELETECLASS(A);
BUFFERSIZEMEMBER(A);
};
SETBUFFERSIZE(A,size)
*/
#pragma once
#include <vector>
#include <algorithm>
#include <cassert>
namespace Neo
{
template<class T>
class MemPool
{
public:
static MemPool<T>& Instance(); //返回单件实例
public:
void SetBufferSize(unsigned int size); //设置缓冲池大小
T* Allocate(); //分配
void Free(T* pT); //释放
void Clear(); //清除所有的缓存对象
private:
std::vector<T*> m_listBuffer; //缓存对象
std::vector<T*> m_listToAllocate; //未分配的对象
private: //辅助类,
//删除缓存值的算法
template<class T>
struct DeleteAlgorithm
{
void operator ()(T* pT){::delete pT;}
};
//新建算法
template<class T>
struct NewAlgorithm
{
void operator ()(T* pT){pT = ::new T;}
};
private: //不允许用户自己定义内存池
MemPool(){}
~MemPool(void)//最后清除所有的缓存对象
{Clear();}
MemPool(const MemPool& other);
};
//成员函数实现
template<class T>
inline MemPool<T>& MemPool<T>::Instance()
{
static MemPool<T> anInstance;
return anInstance;
}
//设置缓存池大小
template<class T>
inline void MemPool<T>::SetBufferSize(unsigned int size)
{
m_listBuffer.resize(size);
//为什么这样分配的调用是全局的delete呢??vs2008下测试
//std::for_each(&m_listBuffer[0],&m_listBuffer[m_listBuffer.size()-1],NewAlgorithm<T>());
for (unsigned int i=0;i<m_listBuffer.size();++i)
{
m_listBuffer[i] = ::new T;
}
m_listToAllocate.resize(size);
std::copy(m_listBuffer.begin(),m_listBuffer.end(),m_listToAllocate.begin());
}
//分配内存
template<class T>
inline T* MemPool<T>::Allocate()
{
//内存已经分配完了,
//以后可以考虑重写这个部分,例如动态增长
assert(!m_listToAllocate.empty() &&"out of mem");
T* pT = m_listToAllocate.back();
m_listToAllocate.pop_back();
return pT;
}
//释放对象
template<class T>
inline void MemPool<T>::Free(T* pT)
{
//确保删除的对象在缓存中
assert(std::find(m_listBuffer.begin(),m_listBuffer.end(),pT) != m_listBuffer.end() && "object to free is not exist!!");
//确保只删除一次
assert(std::find(m_listToAllocate.begin(),m_listToAllocate.end(),pT) == m_listToAllocate.end() &&"object has deleted already!!");
m_listToAllocate.push_back(pT);
}
//清除缓存池内容
template<class T>
inline void MemPool<T>::Clear()
{
std::for_each(m_listBuffer.begin(),m_listBuffer.end(),DeleteAlgorithm<T>());
m_listBuffer.clear();
}
}
//一些宏定义,方便使用
//定义重载new
#define NEWCLASS(className) /
void* operator new(size_t)/
{/
return Neo::MemPool<className>::Instance().Allocate();/
}
//定义重载delete
#define DELETECLASS(className)/
void operator delete(void* p)/
{/
Neo::MemPool<className>::Instance().Free(reinterpret_cast<className*>(p));/
}
//设置缓存池大小静态成员函数
#define BUFFERSIZEMEMBER(className) /
static bool SetBufferSize(unsigned int size)/
{/
Neo::MemPool<className>::Instance().SetBufferSize(size);/
return true;/
}
#define SETBUFFERSIZE(className,size) static bool initial##className = className::SetBufferSize(size);
测试程序:请注意debug模式与release模式的区别(Release模式下比较快)
//在写游戏代码时,可以估算一个关卡所需的对象数量,设置一个合适的缓存值
const int bufferSize = 20;
class TestWithAllocated
{
public:
NEWCLASS(TestWithAllocated);
DELETECLASS(TestWithAllocated);
BUFFERSIZEMEMBER(TestWithAllocated);
};
SETBUFFERSIZE(TestWithAllocated,bufferSize);
class Derived : public TestWithAllocated
{
public:
NEWCLASS(Derived);
DELETECLASS(Derived);
BUFFERSIZEMEMBER(Derived);
public:
void fun(){cout << "hello" << endl;}
};
SETBUFFERSIZE(Derived,bufferSize);
class TestWithOutAllocated
{
};
template<class T>
void Allocate(std::vector<T*>& p,int size)
{
for (int i=0;i<size;++i)
{
p[i] = new T;
}
}
template<class T>
void DeAllocate(std::vector<T*>& p,int size)
{
for (int i=0;i<size;++i)
{
delete p[i];
}
}
int main(void)
{
//内存泄漏检查
int tmpdbgflag;
tmpdbgflag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
tmpdbgflag |= _CRTDBG_DELAY_FREE_MEM_DF;
tmpdbgflag |= _CRTDBG_LEAK_CHECK_DF;
_CrtSetDbgFlag(tmpdbgflag);
//测试次数
int testSize =0;
cout << "input test number !!" << endl;
cin >> testSize;
std::vector<TestWithAllocated*> pWA(bufferSize);
std::vector<TestWithOutAllocated*> pWOA(bufferSize);
//计算测试时间
DWORD start = timeGetTime();
//采用了内存池的对象创建测试
for (int i=0;i<testSize;++i)
{
Allocate<TestWithAllocated>(pWA,bufferSize);
DeAllocate<TestWithAllocated>(pWA,bufferSize);
}
DWORD end = timeGetTime();
cout << " test with buffer time is: " << end - start << endl;
//未采用内存池的对象创建测试
start = timeGetTime();
for (int i=0;i<testSize;++i)
{
Allocate<TestWithOutAllocated>(pWOA,bufferSize);
DeAllocate<TestWithOutAllocated>(pWOA,bufferSize);
}
end = timeGetTime();
cout << " test without buffer time is: " << end - start << endl;
//采用了内存池另一组对象创建测试
std::vector<Derived*> pW(bufferSize);
start = timeGetTime();
for (int i=0;i<testSize;++i)
{
Allocate<Derived>(pW,bufferSize);
DeAllocate<Derived>(pW,bufferSize);
}
end = timeGetTime();
cout << " test with buffer time is: " << end - start << endl;
return 0;
}
测试结果:
在我的电脑上当输入测试数量为100000时
采用内存池分配的对象用时70
未采用内存池根本的对象用时565
以上测试在Release模式下完成,因为在debug模式下,我采用了一些算法,确保分配不会出错。所以效率很低