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;
};

}
posted @ 2020-09-06 02:05  seideun  阅读(248)  评论(0)    收藏  举报