Возможность комбинировать композитный шаблон и любопытно повторяющуюся шаблонную схему

У меня есть составная реализация шаблона, используемая для компонентов GUI:

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 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;
  }
}

К сожалению, количество унаследованных классов быстро увеличивается, а дублированный код (в данном примере - только метод detach()) дает мне головную боль.

Есть ли способ очистить методы detach(), сохраняя возвращаемый тип таким же, как объект, на который он вызван?

Я думал о CRTP, но я не могу придумать способ сохранить динамический полиморфизм вместе с полиморфизмом времени компиляции:

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.

Ответ 1

Вы можете добавить один уровень абстракции:

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>
{
    // ...
};

В естественном языке: интерфейс для всех объектов (CObjectBase) имеет частичную реализацию для своих потомков (CObject<Child>), которые просто должны наследовать эту частичную реализацию, уменьшая количество реплицированного кода.

Ответ 2

Я думал о CRTP, но я не могу придумать способ сохранить динамический полиморфизм вместе с полиморфизмом времени компиляции

Вы можете смешивать их, предоставляя виртуальные реализации по умолчанию для определенных интерфейсов с использованием базовых классов стиля CRTP.

Таким образом, у вас есть возможность объединить базовые реализации CRTP (возможно, настроены с дополнительными параметрами шаблона политики) и по-прежнему могут переопределять определенное поведение в унаследованных классах.

Microsoft Библиотека ATL использует это много. Я также использую эту технику в своей государственной машине STTCL.

Ответ 3

Из одного только фрагмента неясно, зачем вам detach() возвращать указатель на поставляемый тип.

Чтобы воспользоваться detach() возвратом поставляемого типа, его нужно вызывать с использованием ссылки на поставляемый тип в любом случае. Вот так:

CSpecificObject* specific_object = new SpecificObject();
// ...
specific_object->detach()->method_declared_in_specific_object();

Но это можно заменить эквивалентом, который работает, даже если detach недействителен:

specific_object->detach();
specific_object->method_declared_in_specific_object();

Если у вас есть ссылка на базовый тип, вы не можете воспользоваться типом возврата detach():

CObject* specific_object = new SpecificObject();
//...
// !!! Won't compile:
specific_object->detach()->method_declared_in_specific_object(); 

По этой причине неясно, каковы преимущества подхода, который вы пытаетесь реализовать.

Сторона не заключается в том, что метод duplicate() вонючий. Он прерывается, когда поставленный класс не перезаписывает его, но использует реализацию по умолчанию из родительского класса. Это может быть признаком того, что что-то не так с дизайном высокого уровня.