shared_ptr 的对象池模式是否可能?
Is an Object Pool pattern of shared_ptr possible?
是否可以创建 shared_ptr 的对象池?
在我的脑海中画出这个草图,我可以看到两种方法,但每种方法都有一个缺陷:
如果 T 对象存储在可重用池中,在 get() 请求的 shared_ptr 中包装 T 的行为将导致控制块被重新分配到每次堆 - 因此打破了对象池的概念。
如果 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); }
隐式发生
是否可以创建 shared_ptr 的对象池? 在我的脑海中画出这个草图,我可以看到两种方法,但每种方法都有一个缺陷:
如果 T 对象存储在可重用池中,在 get() 请求的 shared_ptr 中包装 T 的行为将导致控制块被重新分配到每次堆 - 因此打破了对象池的概念。
如果 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); }
隐式发生