C++テンプレートを使用してビジターパターンを実装することにより、コード内の定型文の量を削減しようとしています。これまで私はこれを思いついた:
class BaseVisitor {
public:
virtual ~BaseVisitor() {}
};
template<typename T>
class Visitor : public BaseVisitor {
public:
virtual void visit(T& /* visitable */) = 0;
};
template<typename Derived>
class Visitable {
public:
void accept(Visitor<Derived>& visitor) {
visitor.visit(static_cast<Derived&>(*this));
}
};
そして、Visitableの各サブクラスは次のようになります。
class Mesh : public Object, public Visitable<Mesh> {};
class Text : public Object, public Visitable<Text> {};
そして最後に、訪問者は次のようになります。
class Renderer : public Visitor<Mesh>, public Visitor<Text> {}
これまでのところ良い...今ここに問題があります:
for(Scene::iterator it = scene.begin(); it != scene.end(); ++it) {
Object& object = static_cast<Object&>(*it);
if(pre_visit(object)) {
object.accept(this); ///Erm, what do I cast to??
post_visit(object);
}
}
Accept()を呼び出すことができるように、どういうわけかVisitableにキャストする必要がありますが、明らかにTが何であるかわかりません。または、Visitableテンプレートに仮想accept()を追加することはできません。これは、どの引数を取るべきかわからないためです。
C++テンプレートの第一人者はこれを機能させる方法を知っていますか?
これは、可変個引数テンプレートを使用してC++ 11で実行できます。ピートの答えから続く:
// Visitor template declaration
template<typename... Types>
class Visitor;
// specialization for single type
template<typename T>
class Visitor<T> {
public:
virtual void visit(T & visitable) = 0;
};
// specialization for multiple types
template<typename T, typename... Types>
class Visitor<T, Types...> : public Visitor<Types...> {
public:
// promote the function(s) from the base class
using Visitor<Types...>::visit;
virtual void visit(T & visitable) = 0;
};
template<typename... Types>
class Visitable {
public:
virtual void accept(Visitor<Types...>& visitor) = 0;
};
template<typename Derived, typename... Types>
class VisitableImpl : public Visitable<Types...> {
public:
virtual void accept(Visitor<Types...>& visitor) {
visitor.visit(static_cast<Derived&>(*this));
}
};
Visitable
のサブクラス:
class Mesh : public Object, public VisitableImpl<Mesh, Mesh, Text> {};
class Text : public Object, public VisitableImpl<Text, Mesh, Text> {};
Visitor
サブクラス:
class Renderer : public Visitor<Mesh, Text> {};
Scene
コンテナのvalue_type
が何であるかは明確ではありませんが、accept
を呼び出すVisitable<Mesh, Text>
への参照またはポインタを取得する必要があります。
for(Scene::iterator it = scene.begin(); it != scene.end(); ++it) {
Visitable<Mesh, Text>& object = static_cast<Visitable<Mesh, Text>&>(*it);
if(pre_visit(object)) {
object.accept(*this);
post_visit(object);
}
}
BaseVisitorは、任意の訪問者が訪問者を削除できるようにする以外は何もしません。代わりに、呼び出し可能なさまざまなaccept
関数のallを提供する訪問者の基本クラスが必要です。 Visitable
がこの訪問者を受け入れるために。
これを行うには、 type list を使用して、訪問者が受け入れることができる型を定義し、型リストを受け取る基本訪問者クラスを作成し、型リストをパラメーターとして訪問者実装に追加します。
例のスケッチ:
// assuming a typelist has typedefs first and second and a
// type 'empty' representing end of type list
template<typename Types>
class Visitor : public Visitor<Types::second> {
public:
// visitor has a visit function for each type in Types
virtual void visit(typename Types::first& visitable) = 0;
};
template<> class Visitor<empty> { };
template<typename Types>
class Visitable{
public:
// base accepts a visitor which can visit any type in Types
virtual void accept(Visitor<Types>& visitor) = 0;
};
template<typename Derived, typename Types>
class VisitableImpl : public Visitable<Types> {
public:
// impl calls specific visit function
virtual void accept(Visitor<Types>& visitor) override {
visitor.visit(static_cast<Derived&>(*this));
}
};
また、テンプレート化されたビジターパターンが必要であり、可変個引数タイプやタイプリストの使用を伴わないソリューションを作成することができました。
// forward declarations for our Visitable interface
class Object;
class Visitor;
// Visitable objects can accept a visitor.
class Visitable
{
public:
virtual ~Visitable() { }
virtual void accept_visitor(Visitor& visitor) = 0;
virtual void accept(Object& obj);
};
// A base class, to allow downcasting
class Object
{
protected:
virtual void _f() { }
};
// Our Visitor class, which will wrap our concrete visitor implementation
class Visitor
{
public:
Visitor(Object* obj);
// Base class for concrete visitors
template<typename D, typename V>
class OfType : public Object
{
public:
void visit(V* visitable) {
D* derived = static_cast<D*>(this);
// "duck-typed" method; if our derived class does not have
// this method, compilation will fail.
derived->on_visit(visitable);
}
};
template<typename D, typename V>
void visit(V* visitable);
private:
Object* m_obj;
};
Visitor::Visitor(Object* obj) : m_obj(obj) { }
template<typename D, typename V>
void Visitor::visit(V* visitable) {
// check if our visitor is able to visit this instance
OfType<D,V>* visitor = dynamic_cast<OfType<D,V>* >(m_obj);
if (visitor) {
visitor->visit(visitable);
}
}
void Visitable::accept(Object& visitor) {
Visitor wrapped(&visitor);
accept_visitor(wrapped);
}
上記のインターフェースを定義したら、訪問可能なオブジェクトの訪問者用に特定のインターフェースを作成し、それらを具象クラスに実装します。
class This;
class ThisVisitor : public Visitor::OfType<ThisVisitor, This>
{
public:
virtual void on_visit(This* item) = 0;
};
class This : public Visitable
{
public:
void accept_visitor(Visitor& visitor) {
visitor.visit<ThisVisitor>(this);
}
};
class That;
class ThatVisitor : public Visitor::OfType<ThatVisitor, That>
{
public:
virtual void on_visit(That* item) = 0;
};
class That : public Visitable
{
public:
void accept_visitor(Visitor& visitor) {
visitor.visit<ThatVisitor>(this);
}
};
class MyVisitor : public ThisVisitor, public ThatVisitor
{
public:
void on_visit(This* item) { printf("This!"); }
void on_visit(That* item) { printf("That!"); }
};
int main(int argc, const char* argv[] {
This item1;
That item2;
MyVisitor visitor;
item1.accept(visitor); // "This!"
item2.accept(visitor); // "That!"
}
ビジターインターフェースを完全にスキップして、具体的なビジターをOfType<Derived, SomeClass>
から直接派生させることもできますが、新しいクラスが定義されているため、ビジターを拡張するには前者を使用する方が良いと思います(That
は気にしないでください)タイプがThatVisitor
である限り、誰がアクセスしますか。