Search code examples
c++classc++11vectorvisitor-pattern

Implementing visitor pattern for a vector of objects in C++


This is a follow-up to this question.

We can implement the visitor pattern for the problem in the previous question, as suggested in this answer:

class Base {
    foo(Parent& p) {
        p.accept(*this);
    }
    virtual void visit(Child_A&) = 0;
    virtual void visit(Child_B&) = 0;
};

class Parent {
    virtual void accept(Base&) = 0;
};

class Child_A: Parent {
    void accept(Base& v) {
        v.visit(*this);
    }
};

class Child_B: Parent {
    void accept(Base& v) {
        v.visit(*this);
    }
};

class Derived_A: Base { 
    void treat_same(Parent&) {
        // ...
    }
    void visit(Child_A& a) {
        treat_same(a);
    }
    void visit(Child_B& b) {
        treat_same(b);
    }
};
class Derived_B: Base { 
    void visit(Child_A&) {
        // ...
    }
    void visit(Child_B&) {
        // ...
    }
};

But now consider if foo expects a std::vector<std::shared_ptr<Parent>> const& as its argument. Then how can we implement visitor pattern for the problem? Is it possible?

EDIT

foo passes std::vector<std::shared_ptr<Parent>> const& to another class, state. But if all the objects in the vector are of type Child_A it calls state.method_A, if all objects in the vector are of type Child_B it calls state.method_B and otherwise calls state.method_C. Only state works directly with parent classes.

I hope this clears things a bit.


Solution

  • I'm not quite sure I get what you are after, but I'll give it a go.

    As I understand it you want something like the following in your code:

    std::vector<std::shared_ptr<Parent>> children;
    Base * handler = new Derived_A; // or new Derived_B.
    for (child : children) {
        // You want this call to correctly distinguish 
        // between objects of Child_A and Child_B
        handler->visit(child); 
    

    Unfortunately, C++ does not* allow you to do that. (*keep reading) Since function calls are determined by type and all children are of the type shared_ptr for the purpose of this loop.

    Luckily, all is not lost.


    You can do two things, both requireing to determining the "real" type of child at runtime.

    Option 1: Add a field in Parent identifying how it should be handled.

    This requires something along the lines of the following.

    class Parent {
    public:
        enum class SubType {
            A,
            B,
        };
    
        virtual void accept(Base &) = 0;
    
        // Subclasses must implement this to 
        // allow instances of base to correctly handle the objects.
        virtual SubType handle_as() const = 0;
    };
    

    The implementation of Base would then include something like the following:

    class Base {
         void visit( shared_ptr<Parent> p ) {
             switch( p->handle_as() ) {
             case Parent::SubType::A:
                 this->accept( *static_ptr_cast<Child_A>(p) );
                 break;
             case Parent::SubType::B:
                 this->accept( *static_ptr_cast<Child_B>(p) );
                 break;
         }
         // In addition to your accept(Child_A &) accept(Child_B &) etc.
    };
    

    Option 2: Use RunTime Type Identification. (RTTI).

    The other alternative it to use dynamic cast. Which will enable RTTI in your whole executable, this may be what you want in this case, but be aware that it does incur a small performance + executable size cost.

    dynamic_cast will return a nullptr if the cast is illegal, otherwise it will return a valid pointer.

    In short:

    class Base {
         void visit( shared_ptr<Parent> p ) {
             if ( dynamic_ptr_cast<Child_A>(p) ) {
                 this->accept( *dynamic_ptr_cast<Child_A>(p) );
             } 
             else if ( dynamic_ptr_cast<Child_B>(p) ) {
                 this->accept( *dynamic_ptr_cast<Child_B>(p) );
             } 
         }
         // In addition to your accept(Child_A &) accept(Child_B &) etc.
    };