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

У меня есть составная реализация шаблона, используемая для компонентов 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'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;
}
}

К сожалению, количество унаследованных классов быстро увеличивается, и дублирующий код (в данном примере только метод 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.

11

Решение

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

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>), которые просто должны наследовать эту частичную реализацию, уменьшая объем реплицируемого кода.

6

Другие решения

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

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

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

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

1

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

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

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

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

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() метод вонючий. Он прерывается, когда доставленный класс не перезаписывает его, а использует реализацию по умолчанию из родительского класса. Это может быть признаком того, что что-то не так с дизайном высокого уровня.

1