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

来源:互联网 发布:java生成范围随机数 编辑:程序博客网 时间:2024/06/04 23:34
    对象池技术可以避免在程序的生命期中创建和删除大量对象。如果知道程序需要同一类型的大量对象,而且对象的生命周期都很短,就可以为这些对象创建一个池(pool)进行缓存。只要代码中需要一个对象,就可以向对象池请求.用完此对象时,要把它放回池中。对象池只创建一次对象,因此它们的构造函数只调用一次,而不是每次使用时都调用。因此,当构造函数要完成一些设置动作,而且这些设置可以应用与该对象的多次使用时,对象池就很适用。另外在非构造函数方法调用中要在对象上设置特定于实例的参数时,也很适用采用对象池。

      一个对象池的实现

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

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

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

 

[cpp] view plaincopy
  1. #include <queue>  
  2. #include <vector>  
  3. #include <stdexcept>  
  4. #include <memory>  
  5.   
  6. using std::queue;  
  7. using std::vector;  
  8.   
  9. //  
  10. // template class ObjectPool  
  11. //  
  12. // Provides an object pool that can be used with any class that provides a  
  13. // default constructor.  
  14. //  
  15. // The object pool constructor creates a pool of objects, which it hands out  
  16. // to clients when requested via the acquireObject() method. When a client is  
  17. // finished with the object it calls releaseObject() to put the object back  
  18. // into the object pool.  
  19. //  
  20. // The constructor and destructor on each object in the pool will be called only  
  21. // once each for the lifetime of the program, not once per acquisition and release.  
  22. //  
  23. // The primary use of an object pool is to avoid creating and deleting objects  
  24. // repeatedly. The object pool is most suited to applications that use large   
  25. // numbers of objects for short periods of time.  
  26. //  
  27. // For efficiency, the object pool doesn't perform sanity checks.  
  28. // Expects the user to release every acquired object exactly once.  
  29. // Expects the user to avoid using any objects that he or she has released.  
  30. //  
  31. // Expects the user not to delete the object pool until every object  
  32. // that was acquired has been released. Deleting the object pool invalidates  
  33. // any objects that the user had acquired, even if they had not yet been released.  
  34. //  
  35. template <typename T>  
  36. class ObjectPool  
  37. {  
  38.  public:  
  39.   //  
  40.   // Creates an object pool with chunkSize objects.  
  41.   // Whenever the object pool runs out of objects, chunkSize  
  42.   // more objects will be added to the pool. The pool only grows:  
  43.   // objects are never removed from the pool (freed), until  
  44.   // the pool is destroyed.  
  45.   //  
  46.   // Throws invalid_argument if chunkSize is <= 0.  
  47.   //  
  48.   ObjectPool(int chunkSize = kDefaultChunkSize)  
  49.     throw(std::invalid_argument, std::bad_alloc);  
  50.   
  51.   //  
  52.   // Frees all the allocated objects. Invalidates any objects that have  
  53.   // been acquired for use.  
  54.   //  
  55.   ~ObjectPool();  
  56.   
  57.   //  
  58.   // Reserve an object for use. The reference to the object is invalidated  
  59.   // if the object pool itself is freed.  
  60.   //   
  61.   // Clients must not free the object!  
  62.   //  
  63.   T& acquireObject();  
  64.   
  65.   //  
  66.   // Return the object to the pool. Clients must not use the object after  
  67.   // it has been returned to the pool.  
  68.   //  
  69.   void releaseObject(T& obj);  
  70.   
  71.  protected:  
  72.   //  
  73.   // mFreeList stores the objects that are not currently in use  
  74.   // by clients.  
  75.   //  
  76.   queue<T*> mFreeList;  
  77.   //  
  78.   // mAllObjects stores pointers to all the objects, in use  
  79.   // or not. This vector is needed in order to ensure that all  
  80.   // objects are freed properly in the destructor.  
  81.   //  
  82.   vector<T*> mAllObjects;  
  83.   
  84.   int mChunkSize;  
  85.   static const int kDefaultChunkSize = 10;  
  86.   
  87.   //  
  88.   // Allocates mChunkSize new objects and adds them  
  89.   // to the mFreeList.  
  90.   //  
  91.   void allocateChunk();  
  92.   static void arrayDeleteObject(T* obj);  
  93.   
  94.  private:  
  95.   // Prevent assignment and pass-by-value  
  96.   ObjectPool(const ObjectPool<T>& src);  
  97.   ObjectPool<T>& operator=(const ObjectPool<T>& rhs);  
  98. };  
  99.   
  100. template<typename T>  
  101. const int ObjectPool<T>::kDefaultChunkSize;  
  102.   
  103. template <typename T>  
  104. ObjectPool<T>::ObjectPool(int chunkSize) throw(std::invalid_argument,  
  105.     std::bad_alloc) : mChunkSize(chunkSize)  
  106. {  
  107.     if (mChunkSize <= 0) {  
  108.         throw std::invalid_argument("chunk size must be positive");  
  109.     }  
  110.     // Create mChunkSize objects to start  
  111.     allocateChunk();  
  112. }  
  113.   
  114. //  
  115. // Allocates an array of mChunkSize objects because that's  
  116. // more efficient than allocating each of them individually.  
  117. // Stores a pointer to the first element of the array in the mAllObjects  
  118. // vector. Adds a pointer to each new object to the mFreeList.  
  119. //  
  120. template <typename T>  
  121. void ObjectPool<T>::allocateChunk()  
  122. {  
  123.     T* newObjects = new T[mChunkSize];  
  124.     mAllObjects.push_back(newObjects);  
  125.     for (int i = 0; i < mChunkSize; i++) {  
  126.         mFreeList.push(&newObjects[i]);  
  127.     }  
  128. }  
  129.   
  130. //  
  131. // Freeing function for use in the for_each algorithm in the  
  132. // destructor.  
  133. //  
  134. template<typename T>  
  135. void ObjectPool<T>::arrayDeleteObject(T* obj)  
  136. {  
  137.     delete [] obj;  
  138. }  
  139.   
  140. template <typename T>  
  141. ObjectPool<T>::~ObjectPool()  
  142. {  
  143.     // free each of the allocation chunks  
  144.     for_each(mAllObjects.begin(), mAllObjects.end(), arrayDeleteObject);  
  145. }  
  146.   
  147. template <typename T>  
  148. T& ObjectPool<T>::acquireObject()  
  149. {  
  150.     if (mFreeList.empty()) {  
  151.         allocateChunk();  
  152.     }  
  153.     T* obj = mFreeList.front();  
  154.     mFreeList.pop();  
  155.     return (*obj);  
  156. }  
  157.   
  158. template <typename T>  
  159. void ObjectPool<T>::releaseObject(T& obj)  
  160. {  
  161.     mFreeList.push(&obj);  
  162. }  

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

[cpp] view plaincopy
  1. template<typename T>  
  2. const int ObjectPool<T>::kDefaultChunkSize;  

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

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

[cpp] view plaincopy
  1. template <typename T>  
  2. ObjectPool<T>::ObjectPool(int chunkSize) throw(std::invalid_argument,  
  3.     std::bad_alloc) : mChunkSize(chunkSize)  
  4. {  
  5.     if (mChunkSize <= 0) {  
  6.         throw std::invalid_argument("chunk size must be positive");  
  7.     }  
  8.     // Create mChunkSize objects to start  
  9.     allocateChunk();  
  10. }  

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

[cpp] view plaincopy
  1. //  
  2. // Allocates an array of mChunkSize objects because that's  
  3. // more efficient than allocating each of them individually.  
  4. // Stores a pointer to the first element of the array in the mAllObjects  
  5. // vector. Adds a pointer to each new object to the mFreeList.  
  6. //  
  7. template <typename T>  
  8. void ObjectPool<T>::allocateChunk()  
  9. {  
  10.     T* newObjects = new T[mChunkSize];  
  11.     mAllObjects.push_back(newObjects);  
  12.     for (int i = 0; i < mChunkSize; i++) {  
  13.         mFreeList.push(&newObjects[i]);  
  14.     }  
  15. }  

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

[cpp] view plaincopy
  1. //  
  2. // Freeing function for use in the for_each algorithm in the  
  3. // destructor.  
  4. //  
  5. template<typename T>  
  6. void ObjectPool<T>::arrayDeleteObject(T* obj)  
  7. {  
  8.     delete [] obj;  
  9. }  
  10.   
  11. template <typename T>  
  12. ObjectPool<T>::~ObjectPool()  
  13. {  
  14.     // free each of the allocation chunks  
  15.     for_each(mAllObjects.begin(), mAllObjects.end(), arrayDeleteObject);  
  16. }  

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

[cpp] view plaincopy
  1. template <typename T>  
  2. T& ObjectPool<T>::acquireObject()  
  3. {  
  4.     if (mFreeList.empty()) {  
  5.         allocateChunk();  
  6.     }  
  7.     T* obj = mFreeList.front();  
  8.     mFreeList.pop();  
  9.     return (*obj);  
  10. }  

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

[cpp] view plaincopy
  1. template <typename T>  
  2. void ObjectPool<T>::releaseObject(T& obj)  
  3. {  
  4.     mFreeList.push(&obj);  
  5. }  

     使用对象池

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

[cpp] view plaincopy
  1. class UserRequest  
  2. {  
  3. public:  
  4.   UserRequest() {}  
  5.   ~UserRequest() {}  
  6.   
  7.   // Methods to populate the request with specific information.  
  8.   // Methods to retrieve the request data.  
  9.   // (not shown)  
  10.            
  11. protected:  
  12.   // data members (not shown)  
  13. };  

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

[cpp] view plaincopy
  1. UserRequest& obtainUserRequest(ObjectPool<UserRequest>& pool)  
  2. {  
  3.   // Obtain a UserRequest object from the pool  
  4.   UserRequest& request = pool.acquireObject();  
  5.   
  6.   // populate the request with user input  
  7.   // (not shown)  
  8.   
  9.   return (request);  
  10. }  
  11.   
  12. void processUserRequest(ObjectPool<UserRequest>& pool, UserRequest& req)  
  13. {  
  14.   // process the request  
  15.   // (not shown)  
  16.   
  17.   // return the request to the pool  
  18.   pool.releaseObject(req);  
  19. }  
  20.   
  21. int main(int argc, char** argv)  
  22. {  
  23.   ObjectPool<UserRequest> requestPool(1000);  
  24.   
  25.   // Set up program  
  26.   // (not shown)  
  27.   
  28.   while (true /* program is running */) {  
  29.     UserRequest& req = obtainUserRequest(requestPool);  
  30.     processUserRequest(requestPool, req);  
  31.   }  
  32.   
  33.   return (0);  
  34. }  

     另外,,,使用线程池也是一个提高C++程序效率的不错方式。线程池和对象池很相似,即不在程序的整个生命期中动态地创建和删除线程,而是创建一个线程池,按需使用池中的线程。如果程序要处理到来的网络请求,这种程序中常常会用到这种技术.web 服务器就可以维护一个线程池,以备查找页面,从而对到来的各个客户请求作出反应。
0 0
原创粉丝点击