Вариант 1 не потребует от вас изменения V
каждый раз, когда класс добавляется в иерархию в A
.Кроме того, если добавленный класс не реализует clone
, вы получите хорошую блестящую ошибку компилятора вместо того, чтобы все создавалось и терпело неудачу во время выполнения, как в варианте 2.
Так вариант 1лучше.Но вы правы, это несколько повторяется.Вы должны написать похожий код для множества разных типов.К счастью, в C ++ есть механизм для этого: шаблоны.
Используя класс CRTP , мы можем автоматически реализовать функцию clone
.Все, что нужно D1
и D2
, - это наследовать от посредника, а не от A
напрямую:
class A {
public:
virtual A* clone() const = 0;
virtual ~A() = default;
};
template<class C>
struct AClone : A {
A* clone() const override {
return new C(*static_cast<C const*>(this));
}
};
class D1 : public AClone<D1> {
public:
int x1 = 0;
};
class D2 : public AClone<D2> {
public:
int x2 = 2;
};
Выше приведено использование необработанных указателей, и, вероятно, оно будет улучшеновернув вместо этого unique_ptr
, но эта идея сводится к краткости.
Можно также добавить немного защитного программирования к этой функции clone
.
static_assert(std::is_convertible<C*, A*>::value,"");
static_assert(std::is_convertible<C*, AClone*>::value,"");
// These two check `C` is derived unambiguasly from `A` via this specialization
assert(typeid(C) == typeid(*this));
// Check the most derived type is as expected, suggested by Deduplicator
И выможно посмотреть вживую, здесь .