思路:
针对引用计数对象:
static MemoryPool<Sprite> _spritePool;
static MemoryPool<Label> _LabelPool;
// 可代替所有new Ref的地方,比如:
// new(nothrow) Sprite
// new(nothrow) Label
// 创建时:从内存池获得内存块进行构造
// referencecount=0要被delete时,析构对象然后回收内存。
auto sprite = _spritePool.createRef(...); // 三个点是构造函数的参数,任意类型任意数量
auto label = _LabelPool.createRef(...);
// 创建多个,...参数可为空(执行默认构造),不为空,则每个对象都用这些参数构造。
std::vector<Sprite*> sprites = _spritePool.createRefN(10,...);
针对非引用计数对象
class MotherFucker
{
......
}
static MemoryPool<MotherFucker> fuckerPool;
// 和new效果类似,分以下几个步骤:
// 1、从内存池中获得内存块
// 2、对内存块进行构造(......为任意类型任意数量参数,传入构造函数,可以为空)
// 3、返回
auto shit = fuckerPool.newElement(......);、
// 和delete效果类似:
// 1、执行shit的析构
// 2、回收内存
fuckerPool.deleteElement(shit);
这样设计的好处:
1、避免了new/delete的性能消耗
使用前,游戏的内存占用可能波动频率比较高,因为不断的new/delete,这里会有内存调度计算,还会内存碎片化。
使用后,提前分配一块固定大小内存,满足不久的未来的使用,明显减少内存调度和内存碎片化。可能内存占用上会多一些。可以自定义内存管理算法,更高效。
屏蔽new/delete函数(重载),全部走内存池,可以统一跟踪动态内存,避免内存泄露。
不足
一种对象类型一个内存池,通过template模板设计,可能会让代码比较臃肿。
如果要彻底的字节为单位的内存池,那就要重载new/delete,根据EffectiveC++作者的建议是“别作”,设计难度比较大。举个例子,重载一个类的delete/new,子类也会调用这个new/delete,根据参数识别难度大。
一次只能分配一个对象,无法做到new[]一样分配一个地址连续的数组,需要重载new/delete,设计成本与产出可能不成比例。
可多次分配,也可以组成一个vector。
实现原理
1、std::allocator,
2、new的定位形式,new(p) P();,用P的构造函数去构造p指针指向的内存,其实就是内存分配与构造分离。allocator的底层也是这个原理。