Получены любопытно повторяющиеся шаблоны и ковариации

Предположим, у меня есть базовый класс, который клонирует производные классы:

class Base
{
public:
virtual Base * clone()
{
return new Base();
}

// ...
};

У меня есть набор производных классов, которые реализованы с использованием любопытно повторяющегося шаблона:

template <class T>
class CRTP : public Base
{
public:
virtual T * clone()
{
return new T();
}

// ...
};

И я пытаюсь извлечь из этого следующее:

class Derived : public CRTP<Derived>
{
public:
// ...
};

Я получаю ошибки компиляции с эффектом:

error C2555: 'CRTP<T>::clone': overriding virtual function return type differs and is not covariant from 'Base::clone'

Я понимаю, что это, вероятно, результат того, что компилятор не полностью знает дерево наследования для Derived при создании экземпляра CRTP. Кроме того, замена типа возврата (T *) на (Base *) также компилируется. Тем не менее, я хотел бы знать, есть ли работа, которая сохраняет вышеуказанную семантику.

7

Решение

Не очень красивый обходной путь.

class Base
{
protected:
virtual Base * clone_p()
{
return new Base();
}
};template <class T>
class CRTP : public Base
{
protected:
virtual CRTP* clone_p()
{
return new T;
}
public:
T* clone()
{
CRTP* res = clone_p();
return static_cast<T*>(res);
}
};class Derived : public CRTP<Derived>
{
public:
};

использование dynamic_cast<> вместо static если вы чувствуете, что это безопаснее.

3

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

Если вы можете использовать другой синтаксис для указания полных типов, вы можете сделать следующее (предупреждение: непроверенный код):

Давайте сначала начнем с машин:

// this gives the complete type which needs to be used to create objects
// and provides the implementation of clone()
template<typename T> class Cloneable:
public T
{
public:
template<typename... U> Cloneable(U&&... u): T(std::forward<U>(u) ...) {}
T* clone() { return new Cloneable(*this); }
private:
// this makes the class complete
// Note: T:: to make it type dependent, so it can be found despite not yet defined
typename T::CloneableBase::CloneableKey unlock() {}
};

// this provides the clone function prototype and also makes sure that only
// Cloneable<T> can be instantiated
class CloneableBase
{
template<typename T> friend class Cloneable;

// this type is only accessible to Clonerable instances
struct CloneableKey {};

// this has to be implemented to complete the class; only Cloneable instances can do that
virtual CloneableKey unlock() = 0;
public:
virtual CloneableBase* clone() = 0;
virtual ~CloneableBase() {}
};

Хорошо, теперь актуальная иерархия классов. Это довольно стандартно; нет промежуточных звеньев CRTP или других осложнений. Однако ни один класс не реализует clone функции, но все наследуют объявление (прямо или косвенно) от CloneableBase,

// Base inherits clone() from CloneableBase
class Base:
public CloneableBase
{
// ...
};

// Derived can inherit normally from Base, nothing special here
class Derived:
public Base
{
// ...
};

Вот как вы тогда создаете объекты:

// However, to create new instances, we actually need to use Cloneable<Derived>
Cloneable<Derived> someObject;
Derived* ptr = new Cloneable<Derived>(whatever);

// Now we clone the objects
Derived* clone1 = someObject.clone();
Derived* clone2 = ptr->clone();

// we can get rid og the objects the usual way:
delete ptr;
delete clone1;
delete clone2;

Обратите внимание, что Cloneable<Derived> это Derived (это подкласс), поэтому вам нужно использовать Cloneable только для строительства, и в противном случае может претендовать на работу с Derived объекты (ну, tyepinfo также будет идентифицировать его как Cloneable<Derived>).

1

По вопросам рекламы [email protected]