Search code examples
c++templatesdesign-patternscompositecrtp

Possibility to mix composite pattern and curiously recurring template pattern


I have a composite pattern implementation, used for GUI components:

class CObject {
private:

  CObject * m_pParent;  
  CObjectContainer * m_pChildren;

  void private_foo() {
    this->foo();
    //Calls private_foo for each child in container.
    m_pChildren->foo();
  }

public:
  virtual void foo() {
    //empty for base class
  }

  virtual CObject * duplicate() {
    //Do duplication code
    return new CObject(*this);
  }

  virtual CObject * detach() {
    //Remove this object (along with it's children)
    //from current tree.
    m_pParent->RemoveChild(this);
    m_pParent = nullptr;
    return this;
  }
}

class CSpecificObject : public CObject {
public:
  virtual void foo() {
    //Specific code for this class
  }

  virtual CSpecificObject * duplicate() {
    //Overload, but the code only calls diferent constructor
    return new CSpecificObject(*this);
  }

  virtual CSpecificObject * detach() {
    //Note the code is identical.
    m_pParent->RemoveChild(this);
    m_pParent = nullptr;
    return this;
  }
}

Unfortunately the number of inherited classes increases rapidly and the duplicate code (in given example only the detach() method) is giving me a headache.

Is there a way to cleanly implement detach() methods, keeping the return type the same as the object, on which it is called?

I was thinking about CRTP, but I can not think of a way to keep the dynamic polymorphism along with compile time polymorphism:

template <Child>
class CObject {
private:
  ...
  Child * detach() {
    m_pParent->RemoveChild(this);
    m_pParent = nullptr;
    return static_cast<Child*>(this);
  }
  ...
}

//Array of CObject* pointers is no longer possible.

Solution

  • You can add one level of abstraction:

    class CObjectBase
    {
        public:
            // Other methods...
            virtual CObjectBase* detach() = 0;
            virtual CObjectBase* duplicate() const = 0;
    };
    
    template <typename Child>
    class CObject : public CObjectBase
    {
        public:
            // ...
            Child* duplicate() const
            {
                return new Child(*static_cast<Child*>(this));
            }
    
            Child* detach()
            {
                m_pParent->RemoveChild(this);
                m_pParent = nullptr;
                return static_cast<Child*>(this); // Cast needed here (inherent to CRTP)
            }
            std::vector<CObjectBase*> children; // Array possible now
            // ...
    };
    
    class MyObject : public CObject<MyObject>
    {
        // ...
    };
    

    In natural language: an interface for all objects (CObjectBase) have a partial implementation for its descendants (CObject<Child>), which just have to inherit this partial implementation, decreasing the amount of replicated code.