开发者

Template refactoring

开发者 https://www.devze.com 2023-02-27 07:01 出处:网络
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

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);
0

精彩评论

暂无评论...
验证码 换一张
取 消