|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionIn my previous article (Template based Generic Pool using C++), I explained how to implement generic pool using templates and C++. There are a few limitations in this pool design, and I thought it would be good learning experience to overcome these limitations by applying the policy based design approach. Readers are assumed to have basic understanding of C++, templates, and STL. I will not go in to details of policy based design. There are plenty of resources available on the Internet. You can also refer to one of the best C++ books, Modern C++ Design by Andrei Alexandrescu, or his article (Policy-Based Class Design in C++). He has explained policy based design in details. Andrei describes policy based class as:
Note:
Let’s find out different requirements for the pool in real life scenario.
Let’s look at each requirement and see how we can fulfill it. For the 1st requirement, we need two types of connection objects to be stored in a pool, connections with ID (identifiable) and connections without ID (Generic). This can be implemented using “ Note: I could have used structure, but class would be clearer to “C” programmers. class WithId { public: static bool HasId() { return true;} }; class WithoutId { public: static bool HasId() { return false;} }; For the 2nd requirement, we need two types of classes. One which expires after certain amount of idle time, other that does not expire through out the life time of the process. This can be implemented by “ class ObjectWithExpiration { public: // is object expire static bool IsExpire() {return true;….} }; class ObjectWithoutExpiration { public: // is expire :no static bool IsExpire(){return false;} }; For 3rd requirement, we can define “ template<class Object> class ObjectCreationDefault //: public ObjectCreationPolicyBase<Object> { public: // create and initialize the object static Object* Create() { return new Object(); } // Uninitialize and destroy the object static Destroy(Object* pObj) { if(pObj) { delete pObj; } pObj = NULL; } // Validate the object if not valid static bool Validate(Object *pObj) { return true; } }; For 4th requirement, we need to implement “ These are the template based classes which use above defined policies. These policy classes are dependent on class “ template<class Object, class HasIdPolicy, template<CLASS> class CreationPolicy, class ObjectExpirationPolicy, typename HolderTraits = ObjectHolderTraits< Object, CreationPolicy,ObjectExpirationPolicy>, typename ContainerTraits = ObjectContainerTraits<SmartHolder, HasIdPolicy> > class ContainerInterfaceBase { public: ContainerInterfaceBase(){} virtual ~ContainerInterfaceBase(){} protected: ....... Container m_oFree; Container m_oReserved; }; template<class Object, class HasIdPolicy = WithId, template<class> class CreationPolicy = ObjectCreationDefault, class ObjectExpirationPolicy = ObjectWithExpiration, typename HolderTraits = ObjectHolderTraits< Object, CreationPolicy,ObjectExpirationPolicy>, typename ContainerTraits = ObjectContainerTraits< Loki::SmartPtr<typename HolderTraits::ObjectHolder>, WithId> > class ContainerInterfaceWithId {.....}; and template<class Object, class HasIdPolicy = WithoutId, template<class> class CreationPolicy = ObjectCreationDefault, class ObjectExpirationPolicy = ObjectWithExpiration, typename HolderTraits = ObjectHolderTraits< Object, CreationPolicy,ObjectExpirationPolicy>, typename ContainerTraits = ObjectContainerTraits< Loki::SmartPtr<typename HolderTraits::ObjectHolder>, WithId> > class ContainerInterfaceWithoutId {.....}; For 5th requirement, we will define “ class PoolSizeFixed { public: // Is Sizable static bool IsSizable() { return false; } ..... }; class PoolSizeDynamic { public: // is pool sizable static bool IsSizable() { return true; } ..... }; We have found the solution to fulfill these requirements. Now, let's look at how can we use these policies. If we want to use the “ The first approach is easier but the disadvantage is that all the connection classes need to be modified to have connection expiration supported which is not feasible. More over, you might want to store some connections/objects which you do not have access to source code… So, I decided to implement the 2nd approach where we will create connection holder class based on “ In the case of connections not expiring, we don’t need to store the timestamp value. We will create two types of connection holder classes, one with connection timestamp as member variables, other without timestamp. Other problem is how do we know which holder class and when to use based on selected policy? Should we create instance of both the classes and use “ To overcome this problem, we need to look at the traits. What are traits? Nathan C. Myers gives short definition of traits in his article (Traits: a new and useful template technique) as:
template<class Object, template<typename> class ObjectCreationPolicy, class ObjectExpirationPolicy> class ObjectHolderTraits; “ We will partially specialize this template with “ template<class Object, template<typename> class ObjectCreationPolicy> class ObjectHolderTraits<Object,ObjectCreationPolicy,ObjectWithExpiration> { class Holder { . . . . ; }; template<class Object, template<typename> class ObjectCreationPolicy> class ObjectHolderTraits<Object,ObjectCreationPolicy,ObjectWithoutExpiration> { class Holder { . . . . ; }; 4th requirement is partially fulfilled by “ We will define object container traits and partially specialize with “ template<class Object, class HasIdPolicy> class ObjectContainerTraits; Here, we have used STL “List” container for without ID policy, and STL “map” container for without ID policy. template<class Object> class ObjectContainerTraits<Object,WithoutId> { public: typedef list<Object> Container; }; template<class Object> class ObjectContainerTraits<Object,WithId> { public: typedef map<string,Object> Container; }; The objects inside the container are stored as smart pointers. There are two reasons.
To solve these problems, smart pointer is the way to go where we don’t have to worry about memory leak and reference counting. I have used It’s time to put all together and implement the Generic Pool. The Pool class is supposed to be implemented as singleton. I decide to use policy based designed There is a really good article (Singleton Pattern: A review and analysis of existing C++ implementations) on The Code Project. The class is defined as: template<class Object, class HasIdPolicy = WithoutId, class PoolSizePolicy = PoolSizeFixed, class ObjectExpirationPolicy = ObjectWithExpiration, template<class> class CreationPolicy = ObjectCreationDefault, typename HolderTraits = ObjectHolderTraits<Object,CreationPolicy, ObjectExpirationPolicy>, typename ContainerTraits = ObjectContainerTraits<Loki::SmartPtr<typename HolderTraits::ObjectHolder>, HasIdPolicy>, typename ContainterInterfaceTraits = ContainerInterfaceTraits<Object, HasIdPolicy> > class PoolMgr { public: void Init(unsigned nPoolSize, unsigned nExpirationTimeSec); Object* Checkout(string &sId) {...} //withid Object* Checkout() {...} //without id bool Checkin(string &sId) {...} //withid bool Checkin(Object *pObj) {...} // without id void ResizePool(unsigned nNewSize){...} //resize/reset private: typename ContainterInterfaceTraits::ContainerInterfacePolicy m_oContainer; }; In this class, I have provided basic functionality of the pool. You can modify it to have custom behavior. E.g., how long caller function should wait before returning when pool is full and it’s not sizable (See 1). If object expiration policy is allowed, you can spawn a thread which will wake up at certain time to clean up the expired connections. Finally, below is the code to test our pool implementation. Please make sure you use the // My connection class. class MyConnection { public: MyConnection() { LOG(LOG_INFO, "MyConnection()"); } ~MyConnection() { LOG(LOG_INFO, "~MyConnection()"); } string & Get() { return m_sString; } void Set(string &sStr) { m_sString = sStr; } private: string m_sString; }; // int main() { // create a singleton using Loki::SingletonHolder //without id { typedef PoolMgr< MyConnection, WithoutId, PoolSizeFixed, ObjectWithExpiration> Pool; Pool *pMgr = &Loki::SingletonHolder:: Instance(); pMgr->Init(1,5); MyConnection *pConn = pMgr->Checkout(); pMgr->Checkin(pConn); pMgr->ResizePool(0); } //with id { typedef PoolMgr< MyConnection, WithId, PoolSizeFixed, ObjectWithExpiration> Pool; Pool *pMgr = &Loki::SingletonHolder:: Instance(); string sId = "1"; pMgr->Init(1,5); MyConnection *pConn = pMgr->Checkout(sId); pMgr->Checkin(sId); pMgr->ResizePool(0); } } Note: I have included Windows version of Loki library along with my project. You can download latest version at SourceForge. References:
Please do vote and comment on this article which will help me to improve quality of my next article. Thanks.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||