タイプに基づいた巨大なスイッチがある場合は常に、代わりに仮想関数を使用する必要があります。
ある種の仮想clone()
関数を導入する必要があります。
#include <iostream>
#include <memory>
struct base
{
virtual ~base() {};
virtual void do_stuff() = 0;
// cloning interface
std::unique_ptr<base> clone() const
{
return std::unique_ptr<base>(do_clone());
}
private:
// actual clone implementation; uses a raw pointer to support covariance
virtual base* do_clone() const = 0;
};
struct derived_A : base
{
void do_stuff() override { std::cout << "stuff in derived_A" << std::endl; }
// purposefully hide the base implementation,
// since we know we'll be returning a derived_A
std::unique_ptr<derived_A> clone() const
{
return std::unique_ptr<derived_A>(do_clone());
}
private:
derived_A* do_clone() const override
{
return new derived_A(*this);
}
};
struct derived_B : base
{
void do_stuff() override { std::cout << "stuff in derived_B" << std::endl; }
// purposefully hide the base implementation,
// since we know we'll be returning a derived_B
std::unique_ptr<derived_B> clone() const
{
return std::unique_ptr<derived_B>(do_clone());
}
private:
derived_B* do_clone() const override
{
return new derived_B(*this);
}
};
#include <vector>
int main()
{
std::vector<std::unique_ptr<base>> v1;
std::vector<std::unique_ptr<base>> v2;
std::unique_ptr<base> x(new derived_A);
v1.push_back(std::move(x));
std::unique_ptr<base> y(new derived_B);
v1.push_back(std::move(y));
v1[0]->do_stuff();
v1[1]->do_stuff();
// clone
v2.push_back(v1[0]->clone());
v2.push_back(v1[1]->clone());
v2[0]->do_stuff();
v2[1]->do_stuff();
}
リターンタイプに共分散が必要です(静的に型付けされたポインターを保持している場合derived_A
、クローンを作成するとderived_A
、冗長なキャストを回避するためにが生成されます)。これが、クローン作成インターフェイスが2つの部分に分割される理由です。std::unique_ptr<base>
と共変であれば、1つで実行できますがstd::unique_ptr<derived>
、それは生のポインターの場合のみです。
繰り返される定型文を隠す方法があると確信しています。それは読者の練習です。
編集:実際には、ここに行きます。それほど難しくない:
#include <memory>
// Note: leaves with a public: access specifier
#define DEFINE_ABSTRACT_CLONEABLE(selfType) \
DEFINE_CLONEABLE_DETAIL(selfType) \
private: \
virtual selfType* do_clone() const = 0; \
\
public:
// Note: leaves with a public: access specifier
#define DEFINE_CLONEABLE(selfType) \
DEFINE_CLONEABLE_DETAIL(selfType) \
private: \
selfType* do_clone() const override \
{ \
return new selfType(*this); \
} \
\
public:
#define DEFINE_CLONEABLE_DETAIL(selfType) \
public: \
std::unique_ptr<selfType> clone() const \
{ \
static_assert(std::is_same<selfType, \
std::decay<decltype(*this)>::type \
>::value, \
"Must specify current class name."); \
\
return std::unique_ptr<selfType>(do_clone()); \
} \
そしてテスト(小さいサイズに注意してください):
#include <iostream>
#include "cloneable.hpp" // or whatever
struct base
{
// readable error: DEFINE_ABSTRACT_CLONEABLE(int);
DEFINE_ABSTRACT_CLONEABLE(base);
virtual ~base() {};
virtual void do_stuff() = 0;
};
struct derived_A : base
{
DEFINE_CLONEABLE(derived_A);
void do_stuff() override { std::cout << "stuff in derived_A" << std::endl; }
};
struct derived_B : base
{
// error: DEFINE_CLONEABLE(derived_B);
DEFINE_ABSTRACT_CLONEABLE(derived_B);
void do_stuff() override { std::cout << "stuff in derived_B" << std::endl; }
virtual void do_thing() = 0; // abstract again
};
struct derived_AA : derived_A
{
DEFINE_CLONEABLE(derived_AA);
void do_stuff() override { std::cout << "stuff in derived_AA" << std::endl; }
};
struct derived_BB : derived_B
{
DEFINE_CLONEABLE(derived_BB);
void do_stuff() override { std::cout << "doing stuff in derived_BB" << std::endl; }
void do_thing() override { std::cout << "doing thing" << std::endl; }
};
int main()
{
std::unique_ptr<derived_AA> x(new derived_AA());
x->do_stuff();
auto xx = x->clone();
xx->do_stuff();
std::unique_ptr<derived_A> xxx = xx->clone();
xxx->do_stuff();
std::unique_ptr<base> xxxx = xxx->clone();
xxxx->do_stuff();
xxxx->clone()->do_stuff();
std::unique_ptr<derived_BB> y(new derived_BB());
y->do_stuff();
y->do_thing();
auto yy = y->clone();
yy->do_stuff();
yy->do_thing();
std::unique_ptr<derived_B> yyy = yy->clone();
yyy->do_stuff();
yyy->do_thing();
std::unique_ptr<base> yyyy = yyy->clone();
yyyy->do_stuff();
// error, lost derived information: yyyy->do_thing();
yyyy->clone()->do_stuff();
}
do_clone
もう1つの改善点は、純粋な仮想の新しい宣言を作成して、さらに派生するクラスにそれを実装させることですが、これは読者に任されています。