最近になってようやく Acyclic Visitor というものを知りました。。。
Acyclic Visitor は GoF の Visitor Pattern の欠点である循環する依存関係を解消したパターンです。
これならテンプレートで書けるんじゃないかと思って、実験的にちょっと書いてみました。
class AcyclicVisitor
{
public:
virtual ~AcyclicVisitor() = 0;
};
inline AcyclicVisitor::~AcyclicVisitor(){}
class VisitorElement
{
public:
virtual ~VisitorElement(){}
virtual void Accept(AcyclicVisitor&) = 0;
};
template<class T>
class Visitor
{
public:
virtual ~Visitor() {}
virtual void Visit(T&) = 0;
};
template<class T>
class Visitable : public VisitorElement
{
public:
Visitable()
{}
explicit
Visitable(const T& element)
: element(element)
{}
virtual void Accept(AcyclicVisitor&);
private:
T element;
};
template<class T>
void Visitable<T>::Accept(AcyclicVisitor& visitor)
{
if (Visitor<T>* v = dynamic_cast<Visitor<T>*>(&visitor))
{
v->Visit(element);
}
}
以下、使用例です。
class MyVisitor : public AcyclicVisitor
, public Visitor<string>, public Visitor<int>, ...
{
public:
virtual void Visit(string& e)
{
...
}
virtual void Visit(int& e)
{
...
}
...
};
void sample()
{
MyVisitor visitor;
Visitable<string> s("test");
Visitable<int> i(10);
...
s.Accept(visitor);
i.Accept(visitor);
...
}
… とまあ、Visitor パターンもテンプレートで実装できそうですね(ふつうの Visitor もある程度テンプレートで書けますが)。
上の Visitable テンプレートクラスでは T をメンバ変数にしていますが、T と透過的に扱いたい場合は T を継承しするという選択肢もあるかと思います。その場合は、Mixin-from-below (Parameterized Base Class) Idiom が使えますね。その他にも、dynamic_cast に失敗した場合、例外(例えば bad_cast とか)を投げるなど、カスタマイズポイントにした方がいいかもしれません。
ちなみに、dynamic_cast で AcyclicVisitor を Visitor<T> にキャストしていますが、この場合、ダウンキャスト(downcast)ではなく、クロスキャスト(cross-cast)と言うそうです(ダウンキャストが基底クラスから派生クラスへのキャストなのに対し、クロスキャストは基底クラスから(多重継承した)別の基底クラスへのキャストです)。また、関連するイディオムに Capability Query Idiom があります。
後日、もうちょっとちゃんとしたのを書いてみたいと思います。。。