Template refactoring
Let's imagine we have several type of elements, and we want to create a 'manager' for every type of them. The manager takes care of the creation, activation/deactivation and removal for any of the elements (we assume the user will no create/destroy the instances of these elements without using the manager. A very simple example of the code would be something like this:
template <class T>
class NonCachedElementMngr
{
public:
NonCachedElementMngr():
开发者_C百科 rmCounter(0)
{}
~ NonCachedElementMngr()
{
T* element = 0;
if(mElements.size() > 0)
{
typename std::set<T*>::iterator it;
for(it = mElements.begin(); it != mElements.end(); ++it)
{
element = *it;
element->deactivate();
delete element;
}
}
}
T* create()
{
T* element = new T();
element->activate();
mElements.insert(element);
return element;
}
bool release(T* element)
{
bool ret = false;
typename std::set<T*>::iterator it;
it = mElements.find(element);
if(it != mElements.end())
{
element->deactivate();
delete element;
mElements.erase(it);
ret = true;
}
return ret;
}
private:
std::set<T*> mElements;
int rmCounter;
};
Let's imagine now that, for a subgroup of objects, apart from the basic operation, we need also to do some caching. For that subgroup of types, we could define another 'manager' like this:
template <class T>
class CachedElementMngr
{
public:
CachedElementMngr():
rmCounter(0)
{}
~CachedElementMngr()
{
T* element = 0;
if(mElements.size() > 0)
{
typename std::set<T*>::iterator it;
for(it = mElements.begin(); it != mElements.end(); ++it)
{
element = *it;
element->removeFromCache(); // <<<<<<<<<<<<<< Different line
element->deactivate();
delete element;
}
}
}
T* create()
{
T* element = new T();
element->storeInCache(); // <<<<<<<<<<<<<< Different line
element->activate();
mElements.insert(element);
return element;
}
bool release(T* element)
{
bool ret = false;
typename std::set<T*>::iterator it;
it = mElements.find(element);
if(it != mElements.end())
{
element->removeFromCache(); // <<<<<<<<<<<<<< Different line
element->deactivate();
delete element;
mElements.erase(it);
ret = true;
}
return ret;
}
private:
std::set<T*> mElements;
int rmCounter;
};
As obvious, both managers are exactly the same, except for the three lines marked as so. How could I refactor this two templates? We know at compile time if a specific type will be cacheable or not. Notice there is also a different line in the destructor. Any feasible proposal (virtual inheritance, template specialization, SFINAE...) would be very welcome.
Factor out that specific behavior into a policy:
#include <set>
struct cached_tag;
struct noncached_tag;
template<typename Tag>
struct ElementMngrCachePolicy;
template<>
struct ElementMngrCachePolicy<cached_tag>
{
template<typename T>
static void removeFromCache(T* const element) { /*impl...*/ }
template<typename T>
static void storeInCache(T* const element) { /*impl...*/ }
};
template<>
struct ElementMngrCachePolicy<noncached_tag>
{
template<typename T>
static void removeFromCache(T* const) { /*do nothing*/ }
template<typename T>
static void storeInCache(T* const) { /*do nothing*/ }
};
template<typename T, typename CachePolicy>
class ElementMngr
{
typedef std::set<T*> elements_t;
public:
ElementMngr() :
rmCounter()
{ }
~ElementMngr()
{
for (typename elements_t::iterator it = mElements.begin(); it != mElements.end(); ++it)
{
T* const element = *it;
CachePolicy::removeFromCache(element);
element->deactivate();
delete element;
}
}
T* create()
{
T* const element = new T();
CachePolicy::storeInCache(element);
element->activate();
mElements.insert(element);
return element;
}
bool release(T* const element)
{
typename elements_t::iterator it = mElements.find(element);
if (it == mElements.end())
return false;
CachePolicy::removeFromCache(element);
element->deactivate();
delete element;
mElements.erase(it);
return true;
}
private:
elements_t mElements;
int rmCounter;
};
template<typename T>
class CachedElementMngr : public ElementMngr<T, ElementMngrCachePolicy<cached_tag> >
{ };
template<typename T>
class NonCachedElementMngr : public ElementMngr<T, ElementMngrCachePolicy<noncached_tag> >
{ };
Use a policy class...
template <class T, typename Policy>
class ElementMngr
{
~ElementMngr()
{
T* element = 0;
if(mElements.size() > 0)
{
typename std::set<T*>::iterator it;
for(it = mElements.begin(); it != mElements.end(); ++it)
{
element = *it;
Policy::cleanup(element);
delete element;
}
}
}
T* create()
{
T* element = new T();
Policy::init(element);
mElements.insert(element);
return element;
}
bool release(T* element)
{
bool ret = false;
typename std::set<T*>::iterator it;
it = mElements.find(element);
if(it != mElements.end())
{
Policy::release(element);
delete element;
mElements.erase(it);
ret = true;
}
return ret;
}
};
Then define two policies, both implementing the init()
, cleanup()
and release()
methods, but one does the extra line, the other doesn't...
EDIT: fixed my pseudo code so that it's more like real code, I wanted to show that you can make Policy
depend on T
too, and then use for example specialization for specific T
, or you don't have to - you can decide how to define the policy....
You have any number of options, but the basic idea is to add polymorphism.
For runtime polymorphism, you have these general choices:
- Strategy Pattern
- Store functors defining whether or not to use the cache (i.e. with
std::function
s) - Template method (which has nothing to do with C++ templates)
You could also use compile time polymorphism, as detailed in Nim's answer
For an example, here is template method:
template <typename T>
class ElementManager
{
std::set<T*> mElements;
int rmCounter;
virtual void CacheStore(T& element) = 0;
virtual void CacheRemove(T& element) = 0;
public:
ElementManager():
rmCounter(0)
{}
~ElementManager()
{
T* element = 0;
if(mElements.size() > 0)
{
typename std::set<T*>::iterator it;
for(it = mElements.begin(); it != mElements.end(); ++it)
{
element = *it;
CacheRemove(element);
element->deactivate();
delete element;
}
}
}
T* create()
{
T* element = new T();
CacheStore(element);
element->activate();
mElements.insert(element);
return element;
}
bool release(T* element)
{
bool ret = false;
typename std::set<T*>::iterator it;
it = mElements.find(element);
if(it != mElements.end())
{
CacheRemove(element);
element->deactivate();
delete element;
mElements.erase(it);
ret = true;
}
return ret;
}
}
template <class T>
class CachedElementMngr : public ElementManager<T>
{
virtual void CacheStore(T& element)
{
element->storeInCache();
}
virtual void CacheRemove(T& element)
{
element->removeFromCache();
}
};
template <class T>
class NonCachedElementMngr : public ElementManager<T>
{
virtual void CacheStore(T& element)
{
//Purposely Empty
}
virtual void CacheRemove(T& element)
{
//Purposely Empty
}
};
Specifically from the example, it seems that class CachedElementMngr
completely contains all the functionality of class NonCachedElementMngr
except those 3 lines mentioned. I would do something like this:
template<typename T>
class NonCachedElementMngr
{
/* put all content of "CachedElementMngr" in your example and below methods */
virtual void removeFromCache(T* p) { /* empty */ }
virtual void storeInCache(T* p) { /* empty */ }
virtual ~NonCachedElementMngr() { /*retain original */ }
};
template<typename T>
class CachedElementMngr : public NonCachedElementMngr<T>
{
// everything is inherited; just add below methods in this class
virtual void removeFromCache(T* p) { p->removeFromCache(); }
virtual void storeInCache(T* p) { p->storeInCache(); }
virtual ~CachedElementMngr() { /*retain original */ }
};
And intead of calling following method as,
p->removeFromCache();
p->storeInCache();
it should be called as
removeFromCache(p);
storeInCache(p);
精彩评论