C++ object-pool that provides items as smart-pointers that are returned to pool upon deletion

非 Y 不嫁゛ 提交于 2019-11-29 20:00:34

Naive implementation

The implementation uses unique_ptr with a custom deleter that returns objects to the pool. Both acquire and release are O(1). Additionally, unique_ptr with custom deleters can be implicitly converted to shared_ptr.

template <class T>
class SharedPool
{
 public:
  using ptr_type = std::unique_ptr<T, std::function<void(T*)> >;

  SharedPool() {}
  virtual ~SharedPool(){}

  void add(std::unique_ptr<T> t) {
    pool_.push(std::move(t));
  }

  ptr_type acquire() {
    assert(!pool_.empty());
    ptr_type tmp(pool_.top().release(),
                 [this](T* ptr) {
                   this->add(std::unique_ptr<T>(ptr));
                 });
    pool_.pop();
    return std::move(tmp);
  }

  bool empty() const {
    return pool_.empty();
  }

  size_t size() const {
    return pool_.size();
  }

 private:
  std::stack<std::unique_ptr<T> > pool_;
};

Example usage:

SharedPool<int> pool;
pool.add(std::unique_ptr<int>(new int(42)));
pool.add(std::unique_ptr<int>(new int(84)));
pool.add(std::unique_ptr<int>(new int(1024)));
pool.add(std::unique_ptr<int>(new int(1337)));

// Three ways to express the unique_ptr object
auto v1 = pool.acquire();
SharedPool<int>::ptr_type v2 = pool.acquire();    
std::unique_ptr<int, std::function<void(int*)> > v3 = pool.acquire();

// Implicitly converted shared_ptr with correct deleter
std::shared_ptr<int> v4 = pool.acquire();

// Note that adding an acquired object is (correctly) disallowed:
// pool.add(v1);  // compiler error

You might have caught a severe problem with this implementation. The following usage isn't unthinkable:

  std::unique_ptr< SharedPool<Widget> > pool( new SharedPool<Widget> );
  pool->add(std::unique_ptr<Widget>(new Widget(42)));
  pool->add(std::unique_ptr<Widget>(new Widget(84)));

  // [Widget,42] acquired(), and released from pool
  auto v1 = pool->acquire();

  // [Widget,84] is destroyed properly, together with pool
  pool.reset(nullptr);

  // [Widget,42] is not destroyed, pool no longer exists.
  v1.reset(nullptr);
  // Memory leak

We need a way to keep alive information necessary for the deleter to make the distinction

  1. Should I return object to pool?
  2. Should I delete the actual object?

One way of doing this (suggested by T.C.), is having each deleter keep a weak_ptr to shared_ptr member in SharedPool. This lets the deleter know if the pool has been destroyed.

Correct implementation:

template <class T>
class SharedPool
{
 private:
  struct External_Deleter {
    explicit External_Deleter(std::weak_ptr<SharedPool<T>* > pool)
        : pool_(pool) {}

    void operator()(T* ptr) {
      if (auto pool_ptr = pool_.lock()) {
        try {
          (*pool_ptr.get())->add(std::unique_ptr<T>{ptr});
          return;
        } catch(...) {}
      }
      std::default_delete<T>{}(ptr);
    }
   private:
    std::weak_ptr<SharedPool<T>* > pool_;
  };

 public:
  using ptr_type = std::unique_ptr<T, External_Deleter >;

  SharedPool() : this_ptr_(new SharedPool<T>*(this)) {}
  virtual ~SharedPool(){}

  void add(std::unique_ptr<T> t) {
    pool_.push(std::move(t));
  }

  ptr_type acquire() {
    assert(!pool_.empty());
    ptr_type tmp(pool_.top().release(),
                 External_Deleter{std::weak_ptr<SharedPool<T>*>{this_ptr_}});
    pool_.pop();
    return std::move(tmp);
  }

  bool empty() const {
    return pool_.empty();
  }

  size_t size() const {
    return pool_.size();
  }

 private:
  std::shared_ptr<SharedPool<T>* > this_ptr_;
  std::stack<std::unique_ptr<T> > pool_;
};

Here's a custom deleter that checks if the pool is still alive.

template<typename T>
class return_to_pool
{
  std::weak_ptr<SharedPool<T>> pool

public:
  return_to_pool(const shared_ptr<SharedPool<T>>& sp) : pool(sp) { }

  void operator()(T* p) const
  {
    if (auto sp = pool.lock())
    {
      try {
        sp->add(std::unique_ptr<T>(p));
        return;
      } catch (const std::bad_alloc&) {
      }
    }
    std::default_delete<T>{}(p);
  }
};

template <class T>
class SharedPool : std::enable_shared_from_this<SharedPool<T>>
{
public:
  using ptr_type = std::unique_ptr<T, return_to_pool<T>>;
  ...
  ptr_type acquire()
  {
    if (pool_.empty())
      throw std::logic_error("pool closed");
    ptr_type tmp{pool_.top().release(), this->shared_from_this()};
    pool_.pop();
    return tmp;
  }
  ...
};

// SharedPool must be owned by a shared_ptr for enable_shared_from_this to work
auto pool = std::make_shared<SharedPool<int>>();

Although the question is old and has already been answered, I have one minor comment on the solution proposed by @swalog.

Deleter functor can result in memory corruption due to double deletion:

void operator()(T* ptr) {
  if (auto pool_ptr = pool_.lock()) {
    try {
      (*pool_ptr.get())->add(std::unique_ptr<T>{ptr});
      return;
    } catch(...) {}
  }
  std::default_delete<T>{}(ptr);
}

unique_ptr created here will be destroyed when exception is caught. Hence,

std::default_delete<T>{}(ptr);

will result in double deletion.

It can be fixed by changing a place of creating unique_ptr from T*:

void operator()(T* ptr) {
  std::unique_ptr<T> uptr(ptr);
  if (auto pool_ptr = pool_.lock()) {
    try {
      (*pool_ptr.get())->add(std::move(uptr));
      return;
    } catch(...) {}
  }
}

Consider using a shared_ptr instead. The only change you'd have to make is to not count auto pointers with more than one owner. Objects that had would aquired from the SharedPool could delete the auto pointer as normal, but the SharedPool would still hold the actual auto pointer.

template <class T>
class SharedPool
{
 public:
  SharedPool(){}
  virtual ~SharedPool(){}

  void add(std::unique_ptr<T> t) {
    pool_.push_back(std::move(t));
  }

  std::shared_ptr<T> acquire() {
    assert(!empty());
    return *std::find_if(pool_.begin(), pool.end(), [](const std::shared_ptr<T>& i){return i.count() == 1;});
  }

  bool empty() const {
    return std::none_of(pool_.begin(), pool_.end(), [](const std::shared_ptr<T>& i){return i.count() == 1;});
  }

 private:
  std::vector<std::shared_ptr<T>> pool_;
};
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!