一个c++对象池的示例
目录
一、前言
二、对象池的实现
三、使用对象池
一、前言
如果程序需要大量同类型的短期对象,这些对象的构造函数开销很大(例如构造函数要创建很大的、预先指定大小的矢量,来存储数据),分析器确认这些对象的内存分配和释放是一个性能瓶颈,就可以为这些对象创建一个对象池或缓存。每当代码中需要一个对象时,可以从池中请求一个。当使用完对象时,将这个对象返回池中。对象池只创建一次对象,因此对象的构造函数只调用一次,而不是每次需要使用时都调用。因此,对象池适用于构造函数需要为很多对象进行一些设置操作的情况,也适用于通过构造函数之外的方法调用为对象设置一些实例特有的参数。
二、对象池的实现
下面提供一个池的类模板的实现,可以在程序中使用这个模板。当构造这个池时,它分配了一大块指定类的对象,并通过acquireObject()方法给出对象。如果调用acquireObject()时没有空闲的对象,这个池会分配另一块对象。acquireObject()返回Object,它是带有自定义删除器的std::shared_ptr。自定义的删除器不会实际释放内存,而只是把对象放回自由对象列表。
对象池实现中最困难的部分是跟踪哪些对象是空闲的,哪些对象正在使用。这个实现采取的方法是将空闲对象保存在一个队列中。每次客户端请求对象时,对象池从队列前取出一个对象给客户。
代码使用标准模板库(STL)中的std::queue类。分配给对象的内存由std::unique_ptr和std::shared_ptr智能指针处理。
下面是类的定义,通过注释讲解了细节部分。注意,这个模板是通过类的类型模板化的,池中的对象通过这个类构造。
//ObjectPool.h
#pragma once
#include <cstddef>
#include <queue>
#include <stdexcept>
#include <memory>
// Provides an object pool that can be used with any class that provides a
// default constructor.
//
// The object pool constructor creates a pool of objects, which it hands out
// to clients when requested via the acquireObject() method. acquireObject()
// returns an Object which is a std::shared_ptr with a custom deleter that
// automatically puts the object back into the object pool when the shared_ptr
// is destroyed and its reference reaches 0.
//
// The constructor and destructor on each object in the pool will be called only
// once each for the lifetime of the program, not once per acquisition and release.
//
// The primary use of an object pool is to avoid creating and deleting objects
// repeatedly. The object pool is most suited to applications that use large
// numbers of objects with expensive constructors for short periods of time, if
// a profiler tells you that allocating and deallocating these objects is a
// bottleneck.
template <typename T>
class ObjectPool
{
public:
// Creates an object pool with chunkSize objects.
// Whenever the object pool runs out of objects, chunkSize
// more objects will be added to the pool. The pool only grows:
// objects are never removed from the pool (freed), until
// the pool is destroyed.
//
// Throws invalid_argument if chunkSize is 0.
// Throws bad_alloc if allocation fails.
ObjectPool(size_t chunkSize = kDefaultChunkSize);
// Prevent assignment and pass-by-value
ObjectPool(const ObjectPool<T>& src) = delete;
ObjectPool<T>& operator=(const ObjectPool<T>& rhs) = delete;
// The type of smart pointer returned by acquireObject().
using Object = std::shared_ptr<T>;
// Reserves an object for use.
Object acquireObject();
private:
// mFreeList stores the objects that are not currently in use by clients.
std::queue<std::unique_ptr<T>> mFreeList;
size_t mChunkSize;
static const size_t kDefaultChunkSize = 10;
// Allocates mChunkSize new objects and adds them to mFreeList.
void allocateChunk();
};
template<typename T>
const size_t ObjectPool<T>::kDefaultChunkSize;
template <typename T>
ObjectPool<T>::ObjectPool(size_t chunkSize)
{
if (chunkSize == 0) {
throw std::invalid_argument("chunk size must be positive");
}
mChunkSize = chunkSize;
// Create mChunkSize objects to start.
allocateChunk();
}
// Allocates mChunkSize new objects.
template <typename T>
void ObjectPool<T>::allocateChunk()
{
for (size_t i = 0; i < mChunkSize; ++i) {
mFreeList.emplace(std::make_unique<T>());
}
}
template <typename T>
typename ObjectPool<T>::Object ObjectPool<T>::acquireObject()
{
if (mFreeList.empty()) {
allocateChunk();
}
// Move next free object from the queue to a local unique_ptr.
std::unique_ptr<T> obj(std::move(mFreeList.front()));
mFreeList.pop();
// Convert the object pointer to an Object (a shared_ptr with
// a custom deleter).
Object smartObject(obj.release(), [this](T* t){
// The custom deleter doesn't actually deallocate the
// memory, but simply puts the object back on the free list.
mFreeList.push(std::unique_ptr<T>(t));
});
// Return the Object.
return smartObject;
}
注意,对象池的用户通过模板参数指定构造对象的类的名称,并通过构造函数指定分配内存块的大小。这个“块大小”控制了一次创建对象的数目。下面是定义kDefaultChunkSize的代码:
template<typename T>
const size_t ObjectPool<T>::kDefaultChunkSize;
类定义中给出的默认值10对于大多数使用者来说可能都太小了。如果程序一次需要数千个对象,就应该使用一个较大的更合理的值。
构造函数验证chunkSize参数,然后调用allocateChunk()辅助方法来获得对象的初始分配:
template <typename T>
ObjectPool<T>::ObjectPool(size_t chunkSize)
{
if (chunkSize == 0) {
throw std::invalid_argument("chunk size must be positive");
}
mChunkSize = chunkSize;
// Create mChunkSize objects to start.
allocateChunk();
}
allocateChunk()方法分配mChunkSize个元素,将指向每个对象的unique_ptr保存在queue中:
// Allocates mChunkSize new objects.
template <typename T>
void ObjectPool<T>::allocateChunk()
{
for (size_t i = 0; i < mChunkSize; ++i) {
mFreeList.emplace(std::make_unique<T>());
}
}
accquireObject()返回空闲列表头部的元素,如果没有空闲元素,则首先调用allocateChunk():
template <typename T>
typename ObjectPool<T>::Object ObjectPool<T>::acquireObject()
{
if (mFreeList.empty()) {
allocateChunk();
}
// Move next free object from the queue to a local unique_ptr.
std::unique_ptr<T> obj(std::move(mFreeList.front()));
mFreeList.pop();
// Convert the object pointer to an Object (a shared_ptr with
// a custom deleter).
Object smartObject(obj.release(), [this](T* t){
// The custom deleter doesn't actually deallocate the
// memory, but simply puts the object back on the free list.
mFreeList.push(std::unique_ptr<T>(t));
});
// Return the Object.
return smartObject;
}
三、使用对象池
下面的应用程序从用户获得执行操作的请求,然后处理这些请求。这种应用程序很可能是图形前端和后台数据库之间的中间件。例如,它可能是航空公司的预订系统或网上银行应用程序的一部分。将每个用户请求编码在一个对象中,例如下面这样的类:
class UserRequest
{
public:
UserRequest() {}
virtual ~UserRequest() {}
// Methods to populate the request with specific information.
// Methods to retrieve the request data.
// (not shown)
private:
//Data members (not shown)
};
可以使用对象池,而不是在程序的整个生命周期中创建和删除大量的请求对象。程序代码如下:
#include "ObjectPool.h"
#include <iostream>
using namespace std;
class UserRequest
{
public:
UserRequest() { mID = ++msCount; cout << "ctor " << mID << endl; }
virtual ~UserRequest() { cout << "dtor " << mID << endl; }
// Methods to populate the request with specific information.
// Methods to retrieve the request data.
// (not shown)
private:
int mID;
static int msCount;
// Other data members (not shown)
};
int UserRequest::msCount = 0;
ObjectPool<UserRequest>::Object obtainUserRequest(ObjectPool<UserRequest>& pool)
{
// Obtain a UserRequest object from the pool.
auto request = pool.acquireObject();
// Populate the request with user input. (not shown)
return request;
}
void processUserRequest(ObjectPool<UserRequest>::Object& req)
{
// Process the request. (not shown)
// Return the request to the pool.
req.reset();
}
int main()
{
ObjectPool<UserRequest> requestPool(10);
cout << "Loop starting." << endl;
for (size_t i = 0; i < 100; ++i) {
auto req = obtainUserRequest(requestPool);
processUserRequest(req);
}
cout << "Loop finished." << endl;
return 0;
}