仮想継承の巧妙な使用により、CloneableMixin::clone が Base::clone をオーバーライドできるようになりますか?
は から派生していないため、ポリモーフィックまたは非表示CloneableMixin<Base,Derived>
のいずれかのメソッドをオーバーライドすることはできません。Base
CloneableMixin<Base,Derived>
Base
一方、あなたCloneableMixin<Base,Derived>
から派生したBase
場合、それをミックスインにする必要はなくなります。
class Derived : public CloneableMixin<Base,Derived> {....};
を継承しBase
ます。
したがって、例のニーズには、ここに示すソリューションで十分です。
#include <iostream>
// cloner v1.0
template <class Base, class Derived>
struct cloner : Base
{
Base *clone() const override {
return new Derived( dynamic_cast<const Derived &>(*this) );
}
~cloner() override {};
};
struct Base
{
virtual Base * clone() const = 0;
Base() {
std::cout << "Base()" << std::endl;
}
virtual ~Base() {
std::cout << "~Base()" << std::endl;
}
};
struct A : cloner<Base,A>
{
A() {
std::cout << "A()" << std::endl;
}
~A() override {
std::cout << "~A()" << std::endl;
}
};
int main()
{
A a;
Base * pb = a.clone();
delete pb;
}
(C++11 ではなく C++03 標準にコンパイルする場合は、override
キーワードの出現箇所を単純に削除できます。)
この解決策は、いくつかのより現実的なクラス階層に分解されます。たとえば、次のTemplate Method Patternの図では:
#include <iostream>
#include <memory>
using namespace std;
// cloner v1.0
template<class B, class D>
struct cloner : B
{
B *clone() const override {
return new D(dynamic_cast<D const&>(*this));
}
~cloner() override {}
};
/* Abstract base class `abstract` keeps the state for all derivatives
and has some pure virtual methods. It has some non-default
constructors.
*/
struct abstract
{
virtual ~abstract() {
cout << "~abstract()" << endl;
}
int get_state() const {
return _state;
}
void run() {
cout << "abstract::run()" << endl;
a_root_method();
another_root_method();
}
virtual void a_root_method() = 0;
virtual void another_root_method() = 0;
virtual abstract * clone() const = 0;
protected:
abstract()
: _state(0) {
cout << "abstract(): state = " << get_state() << endl;
}
explicit abstract(int state) : _state(state) {
cout << "abstract(" << state << ") : state = "
<< get_state() << endl;
}
int _state;
};
/* Concrete class `concrete` inherits `abstract`
and implements the pure virtual methods.
It echoes the constructors of `abstract`. Since `concrete`
is concrete, it requires cloneability.
*/
struct concrete : cloner<abstract,concrete>
{
concrete() {
cout << "concrete(): state = " << get_state() << endl;
}
explicit concrete(int state) : abstract(state) { //<- Barf!
cout << "concrete(" << state << ") : state = "
<< get_state() << endl;
}
~concrete() override {
cout << "~concrete()" << endl;
}
void a_root_method() override {
++_state;
cout << "concrete::a_root_method() : state = "
<< get_state() << endl;
}
void another_root_method() override {
--_state;
cout << "concrete::another_root_method() : state = "
<< get_state() << endl;
}
};
int main(int argc, char **argv)
{
concrete c1;
unique_ptr<abstract> pr(new concrete(c1));
pr->a_root_method();
pr->another_root_method();
unique_ptr<abstract> pr1(pr->clone());
pr1->a_root_method();
return 0;
}
これをビルドしようとすると、コンパイラーはコンストラクターの初期化abstract(state)
時にconcrete
(Barf!
コメントで)、次のようにエラーを出します。
error: type 'abstract' is not a direct or virtual base of 'concrete'
またはその趣旨の言葉。実際、 の直接の基数はconcrete
is not abstract
butcloner<abstract,concrete>
です。ただし、コンストラクターを次のように書き換えることはできません。
/*Plan B*/ explicit concrete(int state) : cloner<abstract,concrete>(state){....}
のようなコンストラクタがないため
cloner<abstract,concrete>::cloner<abstract,concrete>(int)
しかし、コンパイラの診断は修正を示唆しています。これは、仮想継承が役立つ場合があります。の仮想基地abstract
になる必要があり、これは事実上「の名誉直接基地」を意味し、仮想基地を作成するだけでそれを達成できます。concrete
concrete
B
cloner<B,D>
// cloner v1.1
template<class B, class D>
struct cloner : virtual B
{
B *clone() const override {
return new D(dynamic_cast<D const&>(*this));
}
~cloner() override {}
};
これで、クリーンなビルドと出力が得られました。
abstract(): state = 0
concrete(): state = 0
concrete::a_root_method() : state = 1
concrete::another_root_method() : state = 0
concrete::a_root_method() : state = 1
~concrete()
~abstract()
~concrete()
~abstract()
~concrete()
~abstract()
原則として仮想継承に注意し、少なくともアーキテクチャ上の論理的根拠がある場合にのみその使用を保留する十分な理由があります。
この問題を仮想継承なしで処理したい場合は、任意の の任意のコンストラクターcloner<B,D>
をエコーする のコンストラクターがあることを何らかの方法で確認する必要があります。次に、対応するコンストラクターは
、引数が何であれ、その直接ベースを初期化できます。B
B
D
cloner<B,D>
これは C++03 の夢物語ですが、C++11 の可変個引数テンプレート パラメーターの魔法を使えば簡単です。
// cloner v1.2
template<class B, class D>
struct cloner : B
{
B *clone() const override {
return new D(dynamic_cast<D const&>(*this));
}
~cloner() override {}
// "All purpose constructor"
template<typename... Args>
explicit cloner(Args... args)
: B(args...){}
};
これにより、コンストラクターを として書き換えることができ、正しいビルドと実行可能ファイルが再び得られます。concrete
/*Plan B*/