Implementing the visitor pattern using C++ Templates

谁都会走 提交于 2019-11-27 18:49:47

This can be done in C++11 using variadic templates. Continuing from Pete's answer:

// 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));
    }
};

Subclasses of Visitable:

class Mesh : public Object, public VisitableImpl<Mesh, Mesh, Text> {};
class Text : public Object, public VisitableImpl<Text, Mesh, Text> {};

A Visitor subclass:

class Renderer : public Visitor<Mesh, Text> {};

It's not clear what the value_type of your Scene container is but you need to obtain a reference or pointer to Visitable<Mesh, Text> on which to call accept:

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);
    }
}

Your BaseVisitor does nothing for you, other than allowing arbitrary visitees to delete the visitor. Instead, you want to have a base class for the visitor which provides all of the different accept functions that could be called on it, and for the Visitable to accept this visitor.

To do this, you could use a type list to define the types the visitor can accept, have a base visitee class which takes the type list, and add the type list as a parameter to your visitee implementation.

sketch of example:

// 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));
    }
};

I was also in need of a templated Visitor pattern, and was able to create a solution that does not involve the usage of variadic types or type lists.

// 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);
}

After the above interfaces are defined, create specific interfaces for a visitable object's visitor, then implement them in your concrete class:

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!"
}

You could also skip the visitor interfaces entirely and have your concrete visitor derive from OfType<Derived, SomeClass> directly, but I find using the former is better for extending your visitor as new classes are defined (That should not care about who visits it as long as it is of type ThatVisitor).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!