Mix-InによるCloneableの実装

したいこと

  • clone関数を継承によって実装したい。
  • 下記のような感じにしたい。
  • A, Bはクラスはclone関数をオーバーライドしていないけど、Cloneableを継承することで、cloneができるようになる。
  • struct B : public Cloneable とすることで、BはAを継承したい。
struct A : public Cloneable<A> {
public:
	virtual void tell() { std::cout << "A" << std::endl; }
};
struct B : public Cloneable<B, A> {
public:
	virtual void tell() { std::cout << "B" << std::endl; }
};
int main() {
    std::shared_ptr<A> ca(A().clone());
    std::shared_ptr<A> cb(B().clone());
}

単純にすると上手くいかない

  • 次のようにすると「'Cloneable::clone': オーバーライドする仮想関数の戻り値の型が異なり、'Cloneable::clone' の covariant ではありません。」と怒られる。
  • BはAをCloneableを通して、継承しているので、B*はA*の共変戻り値なのだが、CRTPとMixInにより、コンパイル時に判定できないっぽい。
template <typename T, typename Base = T>
struct Cloneable : public Base {
public:
	virtual T* clone() const { return new T(static_cast<const T&>(*this)); }
};

template <typename T>
struct Cloneable<T, T>{
public:
	virtual T* clone() const { return new T(static_cast<const T&>(*this)); }
};

解決方法

  • non-Templateな抽象クラスICloneBaseを定義し、virtualなdoClone()はICloneBase*を返し、non-virtualなclone()はCRTPにより判明している自分の型にstatic_castする。
struct ICloneBase {
private:
	virtual ICloneBase* doClone() const = 0;
};

template <typename T, typename Base = ICloneBase>
struct Cloneable : public Base {
public:
	T* clone() const { return static_cast<T*>(this->doClone()); }
private:
	virtual ICloneBase* doClone() const { return new T(static_cast<const T&>(*this)); }
};


struct A : public Cloneable<A> {
public: 
	virtual void tell() { std::cout << "A" << std::endl; }
};
struct B : public Cloneable<B, A> {
public:
	virtual void tell() { std::cout << "B" << std::endl; }
};
struct C : public Cloneable<C, B> {
public:
	virtual void tell() { std::cout << "C" << std::endl; }
};

int main()
{
	A a;
	B b;
	C c;
	std::shared_ptr<A> ca1(a.clone());
	std::shared_ptr<A> cb1(b.clone());
	std::shared_ptr<A> cc1(c.clone());
	ca1->tell();
	cb1->tell();
	cc1->tell();
	std::shared_ptr<A> ca2(ca1->clone());
	std::shared_ptr<A> cb2(cb1->clone());
	std::shared_ptr<A> cc2(cc1->clone());
	ca2->tell();
	cb2->tell();
	cc2->tell();
	return 0;
}