您的位置:首页 > 编程语言 > C语言/C++

编写高效的C++程序方法之使用对象池

2014-06-22 22:17 477 查看
对象池技术可以避免在程序的生命期中创建和删除大量对象。如果知道程序需要同一类型的大量对象,而且对象的生命周期都很短,就可以为这些对象创建一个池(pool)进行缓存。只要代码中需要一个对象,就可以向对象池请求.用完此对象时,要把它放回池中。对象池只创建一次对象,因此它们的构造函数只调用一次,而不是每次使用时都调用。因此,当构造函数要完成一些设置动作,而且这些设置可以应用与该对象的多次使用时,对象池就很适用。另外在非构造函数方法调用中要在对象上设置特定于实例的参数时,也很适用采用对象池。

一个对象池的实现

这里提供了一个池类对象的模板的实现,你可以在自己的程序中使用这个池实现。这个池在构造时会分配一大块(chunk)指定类的对象(这里的“块”,可以理解为包括许多对象,即一堆对象),并通过 acquireObject( ) 方法交出对象。当客户用完这个对象时,会通过 releaseObject( ) 方法将其返回。如果调用了 acquireObject( ) ,但是没有空闲的对象,池会分配另外一块对象。

对象池实现中最难的一方面是要记录哪些对象是空闲的,哪些对象正在使用。这个实现采用以下做法,即把空闲的对象保存在一个队列中。每次客户请求一个对象时,池就会把队列中第一个对象交个客户。这个池不会显式地跟踪正在使用的对象。它相信客户在用完对象后会正确的把对象交还到池中。另外,这个池在一个向量中记录所有已分配的对象。这个向量仅在撤销池时才会用到,以便是否所有对象的内存,从而避免内存泄露。

以下是类的定义,要注意,这个模板是基于相应的类类型(要在池中构造何种类型的对象)参数化的。

[cpp] view
plaincopy

#include <queue>

#include <vector>

#include <stdexcept>

#include <memory>

using std::queue;

using std::vector;

//

// template class ObjectPool

//

// 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. When a client is

// finished with the object it calls releaseObject() to put the object back

// into the object pool.

//

// 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 for short periods of time.

//

// For efficiency, the object pool doesn't perform sanity checks.

// Expects the user to release every acquired object exactly once.

// Expects the user to avoid using any objects that he or she has released.

//

// Expects the user not to delete the object pool until every object

// that was acquired has been released. Deleting the object pool invalidates

// any objects that the user had acquired, even if they had not yet been released.

//

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.

//

ObjectPool(int chunkSize = kDefaultChunkSize)

throw(std::invalid_argument, std::bad_alloc);

//

// Frees all the allocated objects. Invalidates any objects that have

// been acquired for use.

//

~ObjectPool();

//

// Reserve an object for use. The reference to the object is invalidated

// if the object pool itself is freed.

//

// Clients must not free the object!

//

T& acquireObject();

//

// Return the object to the pool. Clients must not use the object after

// it has been returned to the pool.

//

void releaseObject(T& obj);

protected:

//

// mFreeList stores the objects that are not currently in use

// by clients.

//

queue<T*> mFreeList;

//

// mAllObjects stores pointers to all the objects, in use

// or not. This vector is needed in order to ensure that all

// objects are freed properly in the destructor.

//

vector<T*> mAllObjects;

int mChunkSize;

static const int kDefaultChunkSize = 10;

//

// Allocates mChunkSize new objects and adds them

// to the mFreeList.

//

void allocateChunk();

static void arrayDeleteObject(T* obj);

private:

// Prevent assignment and pass-by-value

ObjectPool(const ObjectPool<T>& src);

ObjectPool<T>& operator=(const ObjectPool<T>& rhs);

};

template<typename T>

const int ObjectPool<T>::kDefaultChunkSize;

template <typename T>

ObjectPool<T>::ObjectPool(int chunkSize) throw(std::invalid_argument,

std::bad_alloc) : mChunkSize(chunkSize)

{

if (mChunkSize <= 0) {

throw std::invalid_argument("chunk size must be positive");

}

// Create mChunkSize objects to start

allocateChunk();

}

//

// Allocates an array of mChunkSize objects because that's

// more efficient than allocating each of them individually.

// Stores a pointer to the first element of the array in the mAllObjects

// vector. Adds a pointer to each new object to the mFreeList.

//

template <typename T>

void ObjectPool<T>::allocateChunk()

{

T* newObjects = new T[mChunkSize];

mAllObjects.push_back(newObjects);

for (int i = 0; i < mChunkSize; i++) {

mFreeList.push(&newObjects[i]);

}

}

//

// Freeing function for use in the for_each algorithm in the

// destructor.

//

template<typename T>

void ObjectPool<T>::arrayDeleteObject(T* obj)

{

delete [] obj;

}

template <typename T>

ObjectPool<T>::~ObjectPool()

{

// free each of the allocation chunks

for_each(mAllObjects.begin(), mAllObjects.end(), arrayDeleteObject);

}

template <typename T>

T& ObjectPool<T>::acquireObject()

{

if (mFreeList.empty()) {

allocateChunk();

}

T* obj = mFreeList.front();

mFreeList.pop();

return (*obj);

}

template <typename T>

void ObjectPool<T>::releaseObject(T& obj)

{

mFreeList.push(&obj);

}

对于这个类定义有几点需要强调。首先,要注意,对象是按引用获取和释放的,而不是按指针,这样可以避免客户通过指针管理或释放对象。接下来,注意对象池的用户通过模板参数来指定可以创建哪一个类的对象(即类名),通过构造函数指定分配的“块大小”。这个“块大小”控制着一次可创建的对象数。以下是定义 kDefaultChunkSize 的代码:

[cpp] view
plaincopy

template<typename T>

const int ObjectPool<T>::kDefaultChunkSize;

根据类定义,默认值 10 对于大多数使用来说可能都太小了.如果程序一次需要成千上万的对象,就应该使用一个更大、更适合的值。

构造函数验证 chunkSize 参数,并调用 allocateChunk( ) 辅助方法来得到起始的对象分配。

[cpp] view
plaincopy

template <typename T>

ObjectPool<T>::ObjectPool(int chunkSize) throw(std::invalid_argument,

std::bad_alloc) : mChunkSize(chunkSize)

{

if (mChunkSize <= 0) {

throw std::invalid_argument("chunk size must be positive");

}

// Create mChunkSize objects to start

allocateChunk();

}

allocateChunk( ) 方法在连续地存储空间中分配 mChunkSize 个元素。它会在一个 mAllObjects vector 中存储对象数组的指针,并把各个对象压至 mFreeLlist queue。

[cpp] view
plaincopy

//

// Allocates an array of mChunkSize objects because that's

// more efficient than allocating each of them individually.

// Stores a pointer to the first element of the array in the mAllObjects

// vector. Adds a pointer to each new object to the mFreeList.

//

template <typename T>

void ObjectPool<T>::allocateChunk()

{

T* newObjects = new T[mChunkSize];

mAllObjects.push_back(newObjects);

for (int i = 0; i < mChunkSize; i++) {

mFreeList.push(&newObjects[i]);

}

}

析构函数只是释放 allocateChunk( ) 中分配的所有对象数组。不过,它使用了 for_each( ) STL算法来做到这一点,在此向 for_each( ) 传递一个arrayDelete( ) 静态方法的指针,这个方法会对各个对象数组具体完成删除调用。

[cpp] view
plaincopy

//

// Freeing function for use in the for_each algorithm in the

// destructor.

//

template<typename T>

void ObjectPool<T>::arrayDeleteObject(T* obj)

{

delete [] obj;

}

template <typename T>

ObjectPool<T>::~ObjectPool()

{

// free each of the allocation chunks

for_each(mAllObjects.begin(), mAllObjects.end(), arrayDeleteObject);

}

acquireObject( ) 会返回空闲列表中的队头对象,如果没有空闲对象则首先调用 allocateChunk( ) 。

[cpp] view
plaincopy

template <typename T>

T& ObjectPool<T>::acquireObject()

{

if (mFreeList.empty()) {

allocateChunk();

}

T* obj = mFreeList.front();

mFreeList.pop();

return (*obj);

}

最后,releaseObject( ) 将对象返回到空闲列表的队尾。

[cpp] view
plaincopy

template <typename T>

void ObjectPool<T>::releaseObject(T& obj)

{

mFreeList.push(&obj);

}

使用对象池

请考虑一个要从用户得到请求并处理这些请求的应用。这个应用很可能是图形前端和后端数据库之间的一个中间件。例如,这可能是一个航空预定系统或一个在线银行应用的一部分。你可能想把每个用户请求编码到一个对象中,这个类可能如下。

[cpp] view
plaincopy

class UserRequest

{

public:

UserRequest() {}

~UserRequest() {}

// Methods to populate the request with specific information.

// Methods to retrieve the request data.

// (not shown)

protected:

// data members (not shown)

};

这里不用在程序的整个生命期中创建和删除大量请求,而是可以使用一个对象池。程序可能如下所示:

[cpp] view
plaincopy

UserRequest& obtainUserRequest(ObjectPool<UserRequest>& pool)

{

// Obtain a UserRequest object from the pool

UserRequest& request = pool.acquireObject();

// populate the request with user input

// (not shown)

return (request);

}

void processUserRequest(ObjectPool<UserRequest>& pool, UserRequest& req)

{

// process the request

// (not shown)

// return the request to the pool

pool.releaseObject(req);

}

int main(int argc, char** argv)

{

ObjectPool<UserRequest> requestPool(1000);

// Set up program

// (not shown)

while (true /* program is running */) {

UserRequest& req = obtainUserRequest(requestPool);

processUserRequest(requestPool, req);

}

return (0);

}

另外,,,使用线程池也是一个提高C++程序效率的不错方式。线程池和对象池很相似,即不在程序的整个生命期中动态地创建和删除线程,而是创建一个线程池,按需使用池中的线程。如果程序要处理到来的网络请求,这种程序中常常会用到这种技术.web 服务器就可以维护一个线程池,以备查找页面,从而对到来的各个客户请求作出反应。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: