Object Pool
Object Pool
上一篇文章里写了一个内存池。我随后意识到一个问题:为什么我没有给内存池加上类型参数呢?一个内存池可以知晓它所分配的对象的类型,就像 std::allocator 一样。
在重构的过程中,另一个问题浮现出来。如果有类型,那么内存池中的 caching 将被复杂化。原来,只有两种 item,分别是 unused 和 allocated。现在,需要有三种 item,从 unused 中分出一种「cached」,表示已经初始化过,但被缓存在内存池里的对象。
从这一点延伸,可以想到很多问题,比如需要支持显式地释放一个对象。这样下去,我的内存池代码就又会迅速膨胀了。这时,我意识到自己违反了「单一权责原则」(Single Responsibility Principle)。于是,我决定另创一个类 Object Pool,在 Memory Pool 的上层创建我想扩展的设施。
下面是代码:
#include "FixedSizeMemoryPool.hpp"
#include <cassert>
#include <vector>
namespace seideun::memory {
/**
* Object pool for storing (caching) items for later use. This pool can only cache items that has been created by it.
*
* This class's methods may return `std::unique_ptr` instead of a raw pointer. The smart pointer automatically returns
* the object back to the pool instead of actually destructing it. If actual destruction is required, call `free`.
*
* TODO: add ExternalObjectPool, which caches externally created objects.
*
* ## What is an Object Pool?
*
* Object creation and destruction can be costly and unnecessary sometimes. In some occasions, we only destruct an
* item just to construct it back later. A primitive approach will do unnecessary memory allocation & de-allocation.
* To make things worse, if the constructors or destructors of the item is too costly (heavy), the efficiency will
* be much more lowered.
*
* This thing called *object pool* exists as a means to "cache" the items as if we have de-allocated them. Later,
* when we need an item of the same type again, we do not have to go over the construction again, but only need to
* pick out the stored item - in perfectly usable state.
*
* Object pool is most helpful in cases when an object needs to keep special invariants to be valid, or the object
* is too big.
*
* @tparam Item The item, i.e. the object to create and store. The C++ standard makes sure that any type has at least
* one byte of space so I do not worry about it being zero sized types.
*/
template <typename Item>
class ObjectPool {
class AutoReturner;
public:
/**
* Construct a new item on uninitialized memory and get a smart pointer to it.
*
* Use this function to explicitly require a new item instead of a cached one.
*
* @return `std::unique_ptr` to the item, which automatically returns the borrowed item back to this pool.
*/
template <typename... Args>
auto init_new_item(Args&&... args) -> std::unique_ptr<Item, AutoReturner> {
auto ptr = static_cast<Item*>(m_memory_pool.alloc());
ptr->Item(std::forward<args>()...);
return std::unique_ptr<Item, AutoReturner>(ptr, AutoReturner(*this));
}
/**
* Get a cached object from the pool. Must ensure that there IS at least one in the pool.
*
* If no item lives in the pool's cache, the behavior is undefined. Therefore, always use it in conjunction with
* `has_cached` unless you are sure there are objects cached.
*/
auto get_cached() -> std::unique_ptr<Item, AutoReturner> {
auto ret = m_cache.back();
m_cache.pop_back();
return std::unique_ptr<Item, AutoReturner>(ret, AutoReturner(*this));
}
/**
* Get a cached object from the pool. If no object in the cache, then default-initialize a new one.
*
* If the object is default-constructable, we can use this function to avoid checking the cache when we only want
* to get one usable instance, not caring where it comes from.
* @return
*/
auto get_cached_or_default_initialized() -> std::unique_ptr<Item, AutoReturner> {
if (has_cached()) { return get_cached(); }
auto ret = static_cast<Item*>(m_memory_pool.alloc());
ret->Item();
return std::unique_ptr<Item, AutoReturner>(ret, AutoReturner(*this));
}
[[nodiscard]] bool has_cached() const { return !m_cache.empty(); }
/**
* Truly destruct an item, instead of caching it.
*
* This is often done if there are some resources held by the item and we want them released.
*
* CAVEAT: the parameter pointer must be previously given out by this pool, not another instance.
*/
void free(std::unique_ptr<Item, AutoReturner>&& previously_given_out_item) {
assert(&previously_given_out_item.get_deleter().m_pool == this);
auto ptr = previously_given_out_item.release();
ptr->~Item();
m_memory_pool.free((void*)ptr);
}
private:
/**
* A deleter for `std::unique_ptr`, which returns the item given out by the pool back to the cache.
*/
class AutoReturner {
public:
explicit AutoReturner(ObjectPool& pool) : m_pool(pool) {}
void operator()(Item* item_given_out) { m_pool.m_cache.push_back(item_given_out); }
private:
friend ObjectPool;
ObjectPool& m_pool;
};
std::vector<Item*> m_cache;
FixedSizeMemoryPool<sizeof(Item), 16> m_memory_pool;
};
}

浙公网安备 33010602011771号