Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Template based Generic Pool using C++

0.00/5 (No votes)
17 Sep 2004 1  
Template based Generic Pool using C++.

Introduction

Many applications use connection/object pool. A program may require a IMAP connection pool and LDAP connection pool. One could easily implement an IMAP connection pool, then take the existing code and implement a LDAP connection pool. The program grows, and now there is a need for a pool of threads. So just take the IMAP connection pool and convert that to a pool of threads (copy, paste, find, replace????). Need to make some changes to the pool implementation? Not a very easy task, since the code has been duplicated in many places. Re-inventing source code is not an intelligent approach in an object oriented environment which encourages re-usability. It seems to make more sense to implement a pool that can contain any arbitrary type rather than duplicating code. How does one do that? The answer is to use type parameterization, more commonly referred to as templates.

C++ templates allow one to implement a generic Pool<T> template that has a type parameter T. T can be replaced with actual types, for example, class ImapConn, and C++ will generate the class Pool<ImapConn>. Changing the implementation of the Pool becomes relatively simple. Once the changes are implemented in the template Pool<T>, they are immediately reflected in the classes Pool<ImapConn>, Pool<LdapConn>, and Pool<Threads>.

Attached demo project contains:

  1. Docs: Source code documentation.
  2. Source code and project files.

This article demonstrates how to implement generic pool using templates. Code is been compiled on Windows as well as Linux. Please feel free to modify and use.

Below are the requirements to implement generic pool:

  • Generic: It should be generic enough to work with any type of resources. E.g., Database Connection pool, Thread pool, other resource pool etc...
  • Pool Size: Size of the pool should be configurable and if required changeable at runtime.
  • Pool Type: If Pool is fixed size, or temporary connection allowed in case of pool is full.
  • Object's lifetime: If user doesn't check-in the resource back, what should be the duration at which the object will be considered as expired and returned back to free resources.
  • Timeout functionality: If Pool is full and temporary connections are not allowed, how long caller function can wait to get object.

The source code contains:

  1. PoolMgr.h which implements the singleton Pool class. It has following functions:
    template<class T> 
    class PoolMgr
    {
        typedef ObjectHolder<T> ObjHolder;  
        typedef list<ObjHolder> ObjList;  
        static Mutex m_mPool; // Mutex for Pool static instance
    
       Mutex m_mData; // Mutex for Pool data
    
    
    public:
        // Get the Instance of pool manager
    
        static PoolMgr<T>* GetInstance()
        {
            if(!m_pPoolMgr) {
               Lock<Mutex> gaurd(m_mPool);
               if(!m_pPoolMgr)
                   m_pPoolMgr = new PoolMgr<T>()
           }
           return m_pPoolMgr;
     
        }
       // delete the pool
    
       static void DeletePool()
       {
           if(m_pPoolMgr) {
               Lock<Mutex> gaurd(m_mPool);
                if(m_pPoolMgr){
                    delete m_pPoolMgr;
              m_pPoolMgr = NULL;
          }
       }
    
        // Initliaze pool
    
        void Init(unsigned nPoolSize, long nExpirationTime, 
                  bool bTempObjAllowed, unsigned nWaitTime = 3)
        {
           �
        }
         // Reset the pool
    
        void ResetPool()
        {
            .....
        }
         // Initliaze pool
    
        void Init(unsigned nPoolSize, long nExpirationTime, 
                  bool bTempObjAllowed, unsigned nWaitTime = 3)
        {
           ...
        }
        
         // Checkout the Object from pool  
    
        T* Checkout()
        {
         ...        
        }
        // checkin the Object into pool                          
    
        void Checkin(T *pObj)
        {
         ...
        }
    private:
        
        static PoolMgr<T> *m_pPoolMgr; // static instance of PoolMgr
    
     
        //private constructor
    
         PoolMgr()
        {
            m_nPoolSize = 0;
            m_nExpirationTime = 600; // in sec
    
            m_bTempObjAllowed = true;
            m_nWaitTime = 3;
        }
        // distructor   
    
        ~PoolMgr()
        {
           
        }
        // pool size : default 0
    
        unsigned m_nPoolSize;
        // wait time: How long calling function can wait to find object
    
        unsigned m_nWaitTime;
        // Object expiration time: default 600
    
        long m_nExpirationTime;
        // if pool is full, is tempobject allowed
    
        bool m_bTempObjAllowed;
        // reserved objects
    
        ObjList m_oReserved;
        // free objects
    
        ObjList m_oFree;
               
    };
    template<class T> PoolMgr<T>* PoolMgr<T>::m_pPoolMgr = NULL;
    //initialize static instance
    
    template<class T> Mutex PoolMgr<T>::m_mPool;
    • static PoolMgr<T>* GetInstance(): which returns the instance of PoolMgr.
    • static void DeletePool(): deletes the pool and frees resources.
    • void Init(unsigned nPoolSize, long nExpirationTime, bool bTempObjAllowed, unsigned nWaitTime): User must initialize the Pool with the following parameters:
      • PoolSize: Size of the Pool.
      • ExpirationTime: Duration in seconds. If object is not used for this duration, object would be considered as expired and would be moved to an available object pool.
      • TempConnAllowd: If Pool is full, should Pool be allowed to create temporary connections.
      • WaitTime: If temporary connection is not allowed and Pool is full, how long caller function can wait to get the connection from the expired connections.
    • void ResetPool(): Release all the resources and reset the pool.
    • T* Checkout(): Check out the resource.
    • void Checkin(T* pObj): Check in the resource.
    • template<class T>: Class PoolMgr contains two list of pools. One is for reserved objects and the other for free objects. In a multithreaded environment, it would avoid locking all the objects instead of specific types. E.g., only all free objects or reserved.
  2. ObjectHolder.h: which contains the object pointer and timestamp. It is a template class of type T which allows storing any generic object class.
    template<class T>
    class ObjectHolder
    {
        public:
              // constructor
    
            ObjectHolder()
            {
               m_nTimeStamp = -1;
                m_pObj = NULL;
            }
     
           // distructor
    
            ~ObjectHolder()
            {
                if(m_pObj) {
                    m_pObj->Release();
                  m_pObj = NULL;
                }
                
            }
     
           //Initliaze object
    
            void InitObject()
            {
              if(!m_pObj) {
                   m_pObj = new T();
                   m_pObj->Init();
              }
            }
                            
                        
        private:
            T *m_pObj;  // object pointer
    
            long  m_nTimeStamp;  // timestamp
    
            
    };
  3. GenericObject.h: This is a sample generic class which is used for testing this pool. User of this pool needs to either implement following methods in their connection/object class or inherit from GenericObject class.
    class GenericObject
    {
        public:
              //constructor
    
              GenericObject() {}
              //destrctor
    
              ~GenericObject() {}
              
              //Initliaze object
    
              virtual void Init() {}
              
               
              //Release the resource related to object
    
              virtual void Release() {}
              
              // Check if object is still usable
    
              virtual bool IsUsable()
              {
                return true;
              }
                
              // If object is not usable, make it usable
    
              virtual bool MakeUsable()
              {
                  if(!IsUsable()) {
                      Init();
                  return true;
              }
               
    };

    Here:

    1. void Init(): Initialize the object. If object needs to make connection, do it in this function.
    2. void Release(): Release the resources.
    3. bool IsUsabled(): Is this object still usable?
    4. bool MakeUsable(): If it is not usable, try to make it usable, and if successful, return true. This will avoid construction of new object if successfully made reusable.
  4. MutexWin.h (Windows) and Mutex.h (Linux):
    // Lock class
    
    template <class T > class Lock 
    {
     T& obj_; // type object
    
    public:
    // Lock
    
     Lock(T& obj):obj_(obj)
     {
      obj_.Lock();
     }
    // Unlock
    
    ~Lock()
    {
      obj_.Unlock();
    }
    };
    // Mutex class 
    
    class Mutex
    {
     public:
     // constructor
    
     Mutex()
     {
       InitializeCriticalSection(&m_mMutex);
      }
      // destructor
    
      virtual ~Mutex()
      {
       DeleteCriticalSection(&m_mMutex);
      }  
    // lock
    
    bool Lock()
    {
      EnterCriticalSection(&m_mMutex);
      return true;
    }
    // unlock
    
    bool Unlock()
    {
      LeaveCriticalSection(&m_mMutex);
      return true;
    }
    private:
      CRITICAL_SECTION m_mMutex; // critical section as mutex
    
      void operator=(Mutex &m_mMutex) {} // private = operator
    
      Mutex( const Mutex &m_mMutex ) {} // private copy constructor
    
    };
  5. main.c: This is a sample main function which gets the instance of PoolMgr and checks out and checks in the GenericObject.
    PoolMgr<GenericObject> *pMgr = PoolMgr<GenericObject>::GetInstance();
    if(pMgr)
    {  
        // Pool size 10, object expiration time 600 sec
    
        // and temporary connection not allowed
    
        pMgr->Init(10,600, false);
        GenericObject *pObj = NULL;
        pObj = pMgr->Checkout();
        pMgr->Checkin(pObj);
        pMgr->ResetPool();
        
       // ExpirationTime test: Pool size is 1, expiration time
    
       // is 10 secs, and temporary connections not allowed.
    
       // Here when you checkout 2nd object, it would wait for 10 seconds
    
       // and the 1st object would be garbage collected
    
       // and moved to free resource.
    
        pMgr->Init(1,10, false);
        GenericObject *pObj = NULL;
        pObj = pMgr->Checkout();
        std::cout << "1st Object checked out " << std::endl;
        pObj = pMgr->Checkout();
        std::cout << "2st Object checked out after 10 secs " << std::endl;
    
        pMgr->ResetPool();
    }
    PoolMgr<GenericObject>::DeletePool(); // delete and free all resources

Please see my other article about pool design: Generic Pool: Policy based design.

History

  • 09/16/2004: Added support for thread synchronization.

Please let me know how this article would have been improved and made more useful.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here