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

前端 未结 4 1857
渐次进展
渐次进展 2020-12-12 18:48

I\'m having fun with c++-ideas, and got a little stuck with this problem.

I would like a LIFO class that manages a pool of resources. When a resource is

相关标签:
4条回答
  • 2020-12-12 18:54

    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_;
    };
    
    0 讨论(0)
  • 2020-12-12 19:03

    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>>();
    
    0 讨论(0)
  • 2020-12-12 19:07

    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(...) {}
      }
    }
    
    0 讨论(0)
  • 2020-12-12 19:11

    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_;
    };
    
    0 讨论(0)
提交回复
热议问题