编程笔记 - 对象池

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

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<typename T, size_t BlockSize>
class ObjectBlock {
private:
    void* _raw;
    std::vector<bool> _used;
    size_t _counter;
    
    T* get(size_t index) {
       	reinterpret_cast<T*>(static_cast<char*>(_raw) + index * sizeof(T));
    }
    
    size_t getIndex(T* ptr) {
        return (reinterpret_cast<char*>(ptr) - reinterpret_cast<char*>(_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<typename T, size_t BlockSize = 64>
class ObjectPool {
private:
    std::vector<std::unique_ptr<ObjectBlock<T, BlockSize>>> _blocks;
    std::mutex _mutex;
public:
    using Deleter = std::function<void(T*)>;
    using Ptr = std::unique_ptr<T, Deleter>;
    
    ObjectPool() = default;
    ~ObjectPool() = default;
    
    Ptr acquire() {
        T* rawPtr = nullptr;
        {
            std::lock_guard<std::mutex> lock(_mutex);
            for (auto& block : _blocks) {
                if (!block->full()) {
                    rawPtr = block->allocate();
                    break;
                }
            }
            if (!rawPtr) {
                auto newBlock = std::make_unique<ObjectBlock<T, BlockSize>>();
                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<std::mutex> lock(_mutex);
        for (auto& block : _blocks) {
            if (block->deallocate(obj) == 0)
                return;
        }
        assert(false && "Attempt to release object not in pool.");
    }
};
posted @ 2025-04-18 16:57  木杉的园子  阅读(104)  评论(0)    收藏  举报