Я хотел бы предоставить надлежащий механизм клонирования для различных иерархий классов. Это кажется разумной мыслью, и я собрал базовое решение с использованием CRTP для реализации необходимых функций clone()
в производных классах.
Я также создал шаблон с аргументом шаблона шаблона, чтобы политики могли контролировать хранение/владение клоном:
template <typename base, typename derived>
struct clone_raw
{
typedef derived * return_type;
static return_type clone(const base * original) { return new derived(static_cast<const derived &>(*original)); }
};
template <typename base, typename derived>
struct clone_shared
{
typedef std::shared_ptr<derived> return_type;
static return_type clone(const base * original) { return std::make_shared<derived>(static_cast<const derived &>(*original)); }
};
template <typename base, typename derived>
struct clone_unique
{
typedef std::unique_ptr<derived> return_type;
static return_type clone(const base * original) { return std::make_unique<derived>(static_cast<const derived &>(*original)); }
};
// derived class CRTP without base CRTP helper
template <typename base, typename derived, template <typename,typename> typename policy = clone_raw>
class clonable : public base
{
public:
// define our derived's parent class
using parent = clonable<base, derived, policy>;
// constructor forwarding (enable all constructors)
using base::base;
// clone using policy
auto clone() const
{
return policy<base, derived>::clone(this);
}
};
Это работает достаточно хорошо, поскольку каждый производный класс должен использовать CRTP для вызова вышеуказанного механизма.
class Example
{
public:
virtual std::shared_ptr<Example> clone() const = 0;
virtual void explain() const = 0;
};
class Ex1 : public clonable<Example, Ex1>
{
public:
Ex1(const char * text) : m_text(text) {}
void explain() const override { std::cout << m_text; }
private:
const char * m_text;
};
class Ex2 : public clonable<Ex1, Ex2>
{
public:
Ex2(const char * text, const char * extra) : parent(text), m_extra(extra) {}
void explain() const override { parent::explain(); std::cout << " " << m_extra; }
private:
const char * m_extra;
};
Однако это оставляет базовому классу необходимость реализации корневого виртуального метода clone(), а это означает, что везде в иерархии политика клонирования должна указываться снова и снова. Это, конечно, анафема передовой практики/здравого смысла/эффективности/правильности по умолчанию/и т.д.
Итак, я подумал, как насчет того, чтобы сделать два шаблона CRTP, которые работают вместе, один для предоставления базовому классу исходного виртуального clone()
с правильной подписью, а затем производный CRTP, который использует свой родительский класс для определения правильной политики клонирования для использования. , так что нужно указать политику только один раз, в корневом классе, и все производные классы будут реализовывать правильное переопределение clone()
, разумно определяя для себя, какая политика используется базовым классом.
Однако я не могу понять, как предоставить шаблон шаблона политики для производных шаблонов CRTP, чтобы им не нужно было принимать какие-либо явные параметры политики - для реализации идеи дизайна.
// base class CRTP
template <typename base, template <typename, typename> typename policy = clone_raw>
class clonable_base : base
{
public:
// define our derived's parent class
using parent = clonable_base<base, policy>;
// constructor forwarding (enable all constructors)
using base::base;
using clone_policy = policy<base, base>;
using clone_type = typename clone_policy::return_type;
// clone using policy
virtual clone_type clone() const = 0
{
return clone_policy::clone(this);
}
};
Итак, вопрос на миллион долларов здесь: как сделать шаблон шаблона policy
доступным для следующего производного CRTP:
// derived class CRTP with base CRTP helper
template <typename base, typename derived>
class clonable_derived : public base
{
public:
// define our derived's parent class
using parent = clonable_derived<base, derived>;
// constructor forwarding (enable all constructors)
using base::base;
using policy = base::policy; // ???
using clone_policy = policy<base,derived>;
using clone_type = typename clone_policy::return_type;
// clone using policy
clone_type clone() const override
{
return clone_policy::clone(this);
}
};
Кажется, все на месте, но я не понимаю, как открыть шаблон шаблона политики, чтобы производные клонируемые типы могли получить к нему доступ для создания экземпляра соответствующей политики для своих базовых/производных пар?!!