shared_ptr 的对象池模式是否可能?

Is an Object Pool pattern of shared_ptr possible?

是否可以创建 shared_ptr 的对象池? 在我的脑海中画出这个草图,我可以看到两种方法,但每种方法都有一个缺陷:

  1. 如果 T 对象存储在可重用池中,在 get() 请求的 shared_ptr 中包装 T 的行为将导致控制块被重新分配到每次堆 - 因此打破了对象池的概念。

  2. 如果 shared_ptr 对象存储在可重用池中,则 shared_ptr 对象必须停止存在才能启动自定义删除器,并且自定义删除器函数只能通过T 指针。所以没有什么可回收的。

是的,这是可能的。但与其让你的游泳池 return std::shared_ptr<T>,我会考虑让它 return boost::intrusive_ptr<T>。您可以让 intrusive_ptr_release() 负责从池中释放该块,然后由您的用户构建 T 以便您可以创建 intrusive_ptr<T>

经过详尽的研究和测试后,我得出结论,没有 合法的方法(从 C++11 或更低版本开始)创建可重用的对象池 shared_ptr<T>的直接。当然,可以很容易地创建一个 T 对象池来为 shared_ptr<T> 服务,但这会导致控制块的每个服务都进行堆分配。

可以间接地创建 shared_ptr<T> 的对象池(这是 只有 的方式我发现这样做)。间接地,我的意思是必须实现自定义 'memory pool' 样式分配器来存储在 shared_ptr<T> 控制块被销毁时释放的内存以供重用。这个分配器然后被用作“shared_ptr”构造函数的第三个参数:

template< class Y, class Deleter, class Alloc > 
   std::shared_ptr( Y* ptr, Deleter d, Alloc alloc );

shared_ptr<T> 仍将是 constructed/allocated 和 deleted/de-allocated 堆内存 - 没有办法阻止它 - 但通过自定义分配器使内存可重用,确定性可以实现内存占用。

您可以将对象存储在池中(例如 unique_ptr)。游泳池 returns a shared_ptr 应要求提供。自定义删除器returns 将数据添加到池中。这里有一个简单的例子:

#ifndef __POOL_H_
#define __POOL_H_

#include <list>
#include <mutex>
#include <algorithm>
#include <memory>
#include <stdexcept>

namespace common {

template<class T, bool grow_on_demand=true>
class Pool 
{
    public:
    Pool(const char* name_p, size_t n) 
        : mutex_m(), free_m(0), used_m(0), name_m(name_p) 
    {
        for (size_t i=0; i<n; i++)
        {
           free_m.push_front( std::make_unique<T>() );
        }
    }

    const char* getName() const
    {
        return name_m.c_str();
    }

    std::shared_ptr<T> alloc()
    {
        std::unique_lock<std::mutex> lock(mutex_m);
        if (free_m.empty() )
        {
            if constexpr (grow_on_demand) 
            {
                free_m.push_front( std::make_unique<T>() );
            }
            else
            {
                throw std::bad_alloc();
            }
        }
        auto it = free_m.begin();
        std::shared_ptr<T> sptr( it->get(), [=](T* ptr){ this->free(ptr); } );
        used_m.push_front(std::move(*it));
        free_m.erase(it);
        return sptr;
    }


    size_t getFreeCount()
    {
        std::unique_lock<std::mutex> lock(mutex_m);
        return free_m.size();
    }

private:

    void free(T *obj)
    {
        std::unique_lock<std::mutex> lock(mutex_m);
        auto it = std::find_if(used_m.begin(), used_m.end(), [&](std::unique_ptr<T> &p){ return p.get()==obj; } );
        if (it != used_m.end())
        {
          free_m.push_back(std::move(*it));
          used_m.erase(it);
        }
        else
        {
            throw std::runtime_error("unexpected: unknown object freed.");
        }
    }

    std::mutex mutex_m;
    std::list<std::unique_ptr<T>> free_m;
    std::list<std::unique_ptr<T>> used_m;
    std::string name_m;
};

}

#endif /* __POOL_H_ */

默认情况下,如果您从空池中分配新对象 (grow_on_demand=true),池会添加新项目。

  • 新池创建 n 个元素并将它们添加到池中(使用默认构造函数)。
  • 使用 mypool.alloc() 您可以从池中获取一个对象。
  • 当不再使用分配的对象时,shared_ptr 自动返回到池中(通过 alloc().
  • 中的 [=](T* ptr){ this->free(ptr); } 隐式发生