11

can anyone show example code before and after to avoid down casting for visitor pattern code ?

Thanks.

0

2 Answers 2

23

A bare, minimalistic example.

Before

class Base {};
class Derived1 : public Base {};
class Derived2 : public Base {};

// Some arbitrary function that handles Base.
void
Handle(Base& obj) {
    if (...type is Derived1...) {
        Derived1& d1 = static_cast<Derived1&>(base);
        std::printf("Handling Derived1\n");
    }
    else if (...type is Derived2...) {
        Derived2& d2 = static_cast<Derived2&>(base);
        std::printf("Handling Derived2\n");
    }
}

This means Base must have some type tag field, or you will be using dynamic_cast to check for each type.

After

// Class definitions
class Visitor;
class Base {
public:
    // This is for dispatching on Base's concrete type.
    virtual void Accept(Visitor& v) = 0;
};
class Derived1 : public Base {
public:
    // Any derived class that wants to participate in double dispatch
    // with visitor needs to override this function.
    virtual void Accept(Visitor& v);
};
class Derived2 : public Base {
public:
    virtual void Accept(Visitor& v);
};
class Visitor {
public:
    // These are for dispatching on visitor's type.
    virtual void Visit(Derived1& d1) = 0;
    virtual void Visit(Derived2& d2) = 0;
};

// Implementation.
void
Derived1::Accept(Visitor& v) {
    v.Visit(*this); // Calls Derived1 overload on visitor
}
void
Derived2::Accept(Visitor& v) {
    v.Visit(*this); // Calls Derived2 overload on visitor
}

That was the framework. Now you implement actual visitor to handle the object polymorphically.

// Implementing custom visitor
class Printer : public Visitor {
    virtual void Visit(Derived1& d1) { std::printf("Handling Derived1\n"); }
    virtual void Visit(Derived2& d2) { std::printf("Handling Derived2\n"); }
};

// Some arbitrary function that handles Base.
void
Handle(Base& obj)
{
    Printer p;
    obj.Accept(p);
}
  1. Accept() is a virtual function that dispatches on the type of obj (first dispatch)
  2. It then calls appropriate overload of Visit(), because inside Accept() you already know the type of your object.
  3. Visit(), in turn, is a virtual function that dispatches on the type of visitor (second dispatch).

Because you have double dispatch (one on object, another on visitor), you don't do any casting. The downside is that any time you add a class to your hierarchy, you have to go and update your visitor class to add an appropriate function to handle the new subclass.

3
  • I understand your explanation but i wonder what is the class hierarchy for before the visitor pattern was applied in the example. I wonder the handle method is in base class or derived class. What is the typical example of derived class implementation ?
    – nicholas
    Commented Jul 16, 2010 at 3:17
  • @peterwkc, I've added the class hierarchy. It's exactly the same, except without the visitor bits. Handle() is just an arbitrary function that goes anywhere (no matter where). Derived class which is a part of the hierarchy must implement Accept() and call a visitor with itself.
    – Alex B
    Commented Jul 16, 2010 at 3:33
  • The last bit is IMO not a downside but an upside actually. It's usually better to be forced in compile time to update Visitor classes than to track the downcasting across dozens of if-else statements. Commented Mar 10, 2020 at 15:32
4

The wikipedia example uses double dispatch, no downcasting.

0

Not the answer you're looking for? Browse other questions tagged or ask your own question.