编程笔记 - 对象池

如果需要在程序中频繁地创建和删除小对象,如果直接对内存进行操作可能会造成较大的时间开销和内部碎片,而对象池是针对这一场景的有效...

如果需要在程序中频繁地创建和删除小对象,如果直接对内存进行操作可能会造成较大的时间开销和内部碎片,而对象池是针对这一场景的有效优化手段。通过提前分配一定数量的对象在对象池中,并在需要时从池中获取对象,使用完毕后再归还给池,可以减少动态分配内存的次数,避免频繁的创建和销毁操作,从而提高性能和资源利用率。

1. 对象池的设计思路

1.1 基本功能

根据上述需求,分析实现一个对象池应该包含的基本功能如下:

构造函数中申请一批对象

析构函数中释放已申请的对象

提供获取和回收对象的接口

进行动态扩容

1.2 高级功能

取决于实际应用场景,对象池可以实现以下高级功能:

并发安全:在多线程环境下,使用互斥锁保证并发访问安全。

2. 对象池的具体实现

2.1 对象定义

以下是使用对象的定义,可以根据实际场景进行修改。

class Object {

private:

// define variables

public:

Object() {

cout << "Object created." << endl;

}

~Object() {

cout << "Object destroyed." << endl;

}

void dosomething() {}

};

2.2 对象块定义

假设内存池以 N 个对象为一组向内存申请空间,下面定义一个对象块进行管理。每个对象块创建时,会创建 N 个对象保存在数组里,并维护一个数组记录每个对象是否被分配。注意,在构造对象块时,使用 C++ 中的 operator new 向内存申请空间,每次分配时使用 placement new 对预分配内存的、空闲的对象进行分配。

template

class ObjectBlock {

private:

void* _raw;

std::vector _used;

size_t _counter;

T* get(size_t index) {

reinterpret_cast(static_cast(_raw) + index * sizeof(T));

}

size_t getIndex(T* ptr) {

return (reinterpret_cast(ptr) - reinterpret_cast(_raw)) / sizeof(T);

}

public:

ObjectBlock() {

_raw = operator new(sizeof(T) * BlockSize);

_used.resize(BlockSize, false);

_counter = 0;

}

~ObjectBlock() {

for (size_t i = 0; i < BlockSize; i++) {

if (_used[i]) {

get(i)->~T();

}

}

operator delete(_raw);

}

T* allocate() {

for (size_t i = 0; i < BlockSize; i++) {

if (!_used[i]) {

_used[i] = true;

_counter++;

T* obj = new (get(i)) T();

return obj;

}

}

return nullptr;

}

int deallocate(T* ptr) {

size_t index = getIndex(ptr);

if (index >= BlockSize || !_used[index])

return 1;

ptr->~T();

_used[index] = false;

_counter--;

return 0;

}

bool full() const { return _counter == BlockSize; }

bool empty() const { return _counter == 0; }

};

2.3 对象池定义

内存池使用数组管理申请的对象块。当需要向池申请一个对象时,遍历每个块,查询其是否已满:如果未满,则分配一个对象并返回指针;如果已满,则申请一块新的对象块添加到数组尾部,在新块中分配一个对象并返回指针。当需要向池释放一个对象时,并不会真正地释放这个对象的内存区域,而是在块中标记为未使用,池需要遍历每个块判断该对象是否属于当前块。

template

class ObjectPool {

private:

std::vector>> _blocks;

std::mutex _mutex;

public:

using Deleter = std::function;

using Ptr = std::unique_ptr;

ObjectPool() = default;

~ObjectPool() = default;

Ptr acquire() {

T* rawPtr = nullptr;

{

std::lock_guard lock(_mutex);

for (auto& block : _blocks) {

if (!block->full()) {

rawPtr = block->allocate();

break;

}

}

if (!rawPtr) {

auto newBlock = std::make_unique>();

rawPtr = newBlock->allocate();

_blocks.push_back(std::move(newBlock));

}

}

return Ptr(rawPtr, [this](T* p){ this->release(p); });

}

void release(T* obj) {

std::lock_guard lock(_mutex);

for (auto& block : _blocks) {

if (block->deallocate(obj) == 0)

return;

}

assert(false && "Attempt to release object not in pool.");

}

};