什么是对象池
类似于内存池机制,对象池中存放有已经分配好的对象,当需要使用时,只需要向对象池申请。而不再使用时,则由对象池自动回收。适用于一下情况:
- 对象可重用
- 对象创建开销大
- 对象创建频繁
如何实现自动回收
C++11中的智能指针可以自定义删除器,在回收时智能指针自动调用自定义的删除器实现对象的自动回收。那么何时定义删除器呢?如果我们在添加对象时定义删除器,那么对象被回收后就失去了自定义的删除器。因此我们可以在获取对象时,定义删除器,如此分配出去的对象总是能够自动回收。另外使用的智能指针是unique_ptr,而不是shared_ptr,因为无法直接将shared_ptr的删除器修改为自定义删除器,虽然可以通过重新创建一个新对象,把原对象拷贝过来的做法来实现,但是这样做效率比较低。以下是实现过程:
class A {
public:
int m;
public:
A(int x = 0) : m(x) { }
~A() {}
};
template <class T>
class SimpleObjectPool
{
public:
using DeleterType = std::function<void(T*)>;
void add(std::unique_ptr<T> t) //向对象池添加一个对象
{
pool_.push_back(std::move(t));
}
std::unique_ptr<T, DeleterType> get() //从对象池获取一个对象
{
try {
if (pool_.empty())
{
throw std::logic_error("no more object");
}
}
catch (logic_error& e) {
cout << e.what() << endl;
return nullptr;
}
//先释放一个对象,然后为其添加一个自定义的删除器。即ptr获得了对象池的一个对象,
//并且拥有了一个自定义的删除器,其功能是将对象放回对象池,而不是销毁
std::unique_ptr<T, DeleterType> ptr(pool_.back().release(), [this](T* t) {
pool_.push_back(std::unique_ptr<T>(t));
});
pool_.pop_back();
return std::move(ptr); //转化为右值,否则无法被赋值
}
bool empty() const
{
return pool_.empty();
}
size_t size() const
{
return pool_.size();
}
private:
std::vector<std::unique_ptr<T>> pool_;
};
void test_object_pool()
{
SimpleObjectPool<A> p;
p.add(std::unique_ptr<A>(new A()));
p.add(std::unique_ptr<A>(new A()));
{
auto t = p.get();
p.get();
}
{
auto t1 = p.get();
auto t2 = p.get();
auto t3 = p.get();
}
std::cout << p.size() << std::endl;
}
int main() {
test_object_pool();
return 0;
}
输出如下:
no more object
2
总结
- 因为申请的对象有自定义的删除器,因此申请的对象离开作用域后,就会被对象池自动回收
- 对象池的对象的删除器是默认的删除器,只有被分配出去的对象才有自定义的删除器,因为这样可以保证对象池在离开作用域后,能够正确的自动释放所有对象的资源。
- 不使用shared_ptr的原因主要是,shared_ptr不能像unique_ptr那样使用release来主动释放资源所有权。只能通过如下方式:
auto p = pool_.back();
shared_ptr<T> ptr(p, [this](T* t) { //语法不正确,因为同一个对象的shared_ptr指针的删除器必须相同,如果正确,
pool_.push_back(shared_ptr<T> t); //那么ptr和p将使用不同的删除器,显然不合理
});
pool_pop_back();
return ptr;
shared_ptr<T> p = pool_.back();
std::shared_ptr<T> ptr(new T(*p.get()), [this](T* t) { //语法正确,但是需要重新创建并拷贝原对象,效率低
pool_.push_back(std::shared_ptr<T>(t));
});
return ptr;