Visitors - are they even useful?

Hi, I just read about "visitors" in Stroustrups book in context of polymorphism. Fo example

1
2
3
4
5
6
7
8
9
10
11
12
13
class Visitor;

class Shape{
public:
   virtual void accept(Visitor&) = 0;
    //...
};

class Circle : public Shape{
public:
    void accept(Visitor&) override;
    //...
};

 
void Circle::accept(Visitor& v){v.visit(*this);}

1
2
3
4
5
6
7
8
9
10
class Visitor{
public:
    void visit(Shape&) = 0;
    //...
};

class CircleVisit : pubic Visitor{
public:
    void visit(Shape& sh) override { /*do some stuff with shape*/}
};


1
2
3
4
5
6
7
8
int main()
{
    Circle C;
    CircleVisitor CVisit;

    C.accept(CVisit);
    C.do_some_stuff();   //<- why just not use this straight away
}


As far as I can tell I can only do public operations on circle in this CircleVisit anyway so whats the point of it. I can instead just use them straight away like
C.doSomeStuff();
instead of going through all the trouble creating this visitor class and in the end accomplishing the same thing. Why is this so useful technique in real world programs?
Hi, I just read about "visitors" in Stroustrups book in context of polymorphism.

And yet, you're only considering a context bereft of polymorphic behavior.

Perhaps you should consider the situation when you have a pointer-to-shape and don't know what derived type it points to.
pubic Visitor
How licentious.

Visitors are useful because sometimes you will have a tree-like structure of heterogeneous types and you will want the same operation to the entire tree recursively. If you have to do that just once, that's fine; just declare the function in all the involved types and recurse manually. If you have to add two such operations, you're probably better off writing a visitor.
Tnx for answers guys!

And yet, you're only considering a context bereft of polymorphic behavior.

Not that there was any info besides similar example to this in the book. And When I was looking to other resources why are they so useful I only saw the same examples. Would be good if you would give some scenarios :)

Perhaps you should consider the situation when you have a pointer-to-shape and don't know what derived type it points to.

Then I can still only use the public interface of Shape even if i use Visitor. What difference does it make
Shape* s; s->draw() or
Shape* s; s->accept(VisitorThatDraws)
What did you mean by this?

pubic Visitor. How licentious.

I havent see all the example of course but the ones I have seen are all public. If they were not how could I even call those Visitors member functions from Shape? Should Visitor be friend of my Shape class?


I can see how they could be useful for applying the same operations for entire tree or array of Shapes. I could pass Visitor as parameter to function that applies this operations for every Shape and I don't have to introduce new function if I want to apply other operations after the first one. So yea tnx for this tip!
Last edited on
I havent see all the example of course but the ones I have seen are all public. If they were not how could I even call those Visitors member functions from Shape? Should Visitor be friend of my Shape class?
That was a joke. Read more closely. Hint: you didn't write "public".
lol man I see it now :D :D
cire wrote:
Perhaps you should consider the situation when you have a pointer-to-shape and don't know what derived type it points to.


etrusks wrote:
Then I can still only use the public interface of Shape even if i use Visitor. What difference does it make


I think you might be missing the concept of base class pointers here. For example, one writes a function that takes a pointer to base as an parameter. One then sends an argument which is a pointer to derived, and this is used to call derived's function in the first instance, or a virtual function higher up in the tree if derived function doesn't exist.

This is because a derived pointer is acceptable in the place of a base pointer. It works by the compiler being able to follow pointers to parent object upwards through the inheritance tree. There is no conversion to base pointer.

So you can use the derived interface, or any other valid virtual function higher in the tree.

This is of tremendous use, one can have a base class with a few pure virtual functions (the interface), but it will still work for any number of derived classes that are added to the inheritance tree, without having an interface that reflects dozens of derived classes.
An example of what I was talking about in my previous post:

You have a std::vector<Shape *> filled with all different kinds of Shape pointer. You then want to calculate the area of them all, so you have a range based for loop which calls the area() function for each pointer. It calls the correct function for each one as I described above.

Hope this helps a bit :+)
Hope this helps a bit :+)

Dude I finally understood this stuff and I see why this can be useful! Thanks a lot for this man! I played around with this a bit and yea I don't just have to use Shapes interface but I can use whatever I want from derived classes interface!

The only thing we have to do is add new pure virtual function to Visitor base class every time new class is derived from existing Shape tree. So if im not implementer of this Visitor class I kind of have to dig it up in a pile of header files right?
Hi,

I should have said, with this particular example - all of your interface should be in the Shape class (or whatever the base class is) with pure virtual functions. In other words, try to push the interface functions as high up the tree as possible.

Imagine a CAD (Computer Assisted Drawing) program like AutoCAD. There might be a base class called DrawingEntities, with classes under that for Shape, Point, LineSegment, Text etc. There are basic modification functions like Move, Rotate, Scale and Stretch, and display functions Draw and Hide, all of which apply to all of the drawing entities, but things like area, perimeter and hatch only apply to shapes. So the placement of which virtual functions go in which class would reflect that.

Also consider a Triangle class, with derived classes for Scalene, Isosceles, Right, Equilateral. The area(Triangle* ATriangle) function is the same for all of them (0.5 * base * height), so it exists in Triangle as a virtual function, and not in any of the derived classes at all. As long as there is a base and height variables to access, this will work with any derived Triangle type passed as an argument. The pure virtual area function in Shape still takes a Shape pointer or reference.

Some might argue that we don't need to derive anything from Triangle. That's fine, as along as one doesn't have to resort to poor man's type-ing by having a member variable which says what the type is.

I should also say that references are supposed to work just as well as pointers, but one can't directly have a container of references. There is std::reference_wrapper for this purpose, but the use of smart pointers might be better.

The only thing we have to do is add new pure virtual function to Visitor base class every time new class is derived from existing Shape tree.


No, I disagree. The visit function is pure virtual, your code was missing the virtual keyword:

1
2
3
4
5
class Visitor{
public:
    virtual void visit(Shape&) = 0;
    //...
};


Because it is pure virtual and takes a Shape reference, it means you explicitly don't have to provide extra base class functions for each derived class. This goes exactly to the point of what I am saying. You don't have to change the interface, only provide an overridden virtual function in whatever derived class that needs it. And that may not be for every derived class as explained earlier.

Some other things about polymorphism:

There must be a virtual destructor, otherwise it's Undefined Behaviour (UB)

The "Rule of Zero" often applies, in that you should default all five special member functions:
http://en.cppreference.com/w/cpp/language/rule_of_three


Note the last sentence, you can get out of it sometimes.

There we go some more stuff to think about :+)
Last edited on
I should have said, with this particular example - all of your interface should be in the Shape class (or whatever the base class is) with pure virtual functions. In other words, try to push the interface functions as high up the tree as possible.

I would say this is bad advice. Place common interface functions in a common base. Don't place an interface function that isn't common in a common base just so you can invoke it with a base class pointer or reference. The visitor pattern gets around this because...

Because it is pure virtual and takes a Shape reference, it means you explicitly don't have to provide extra base class functions for each derived class.

Generally speaking, double dispatch is what you use the visitor pattern for and if you don't provide overloads in the visitor class for the derived classes you get single dispatch, which you can already achieve with a simple function call.

Dude this is amazing how much you have typed for me and I really appreciate it. So I almost don't want to ask few more things :D but here I would be happy with yes no answer as well!

Here when I wrote this i indeed typed
1
2
3
4
5
class Visitor{
public:
    virtual void visit(Shape&) = 0;
    //...
};

but in all the examples and in Stroustrups book Visitor was actually filled with very type specific functions like
1
2
3
4
5
6
7
class Visitor{
public:
    virtual void visit(Circle&) = 0;
    virtual void visit(Polygon&) = 0;
    virtual void visit(Rectangle&) = 0;
    //...
};

And I tested the last one out and it worked very good. Than I tried to override
virtual void visit(Shape&) = 0; using
void visit(Circle&) override;
and compiler told me that 2nd function is declared to override something but doesn't override anything. So I understood that parameter types must match exactly. You can only do this stuff to return types.
Question Nr. 1 (Y/N accepted) : So now if Visitor class has very specific type functions like Circle, Polygon, Rectangle above than if Wave is being introduced I kind of have to add pure virtual function to Visitor coz otherwise Wave cant be associated with any other visit funtion right?

I can see the alternative could be to keep using
virtual void visit(Shape&) = 0; and override it like this in case I want to use it for shape
1
2
3
4
5
6
7
8
9
10
11
12
void visit(Shape& sh) override {
    if(auto p = dynamic_cast<Wave*>(&sh)){
        //do specific stuff that only Wave can do
    }
    else{
        //just do something else
    }

    //or I could just do this to find the right shape
    if(typeid(sh) == typeid(Wave)) {/*...*/}
    else if(typeid(sh) == typeid(Circle)) {/*...*/}
}

This would work but Stroustrup told not to build your programs around Run Time Type Checking where its not necessary.
Question Nr. 2 (Y/N accepted) : I feel like this would probably be very bad way to write a code right?
Last edited on
cire wrote:
... Don't place an interface function that isn't common in a common base just so you can invoke it with a base class pointer or reference. ...


I guess it's a mater of interpretation and expression; I said:
TheIdeasMan wrote:
In other words, try to push the interface functions as high up the tree as possible.


A added some emphasis this time :+)

And this was borne out in my example of the DrawingEntities, I wasn't promoting gratuitous promotion of functions. So IMO we are saying the same thing: > "Place common interface functions in a common base." It's probably more that you have expressed it better than me :+)

You are right about the Visitor Pattern and double dispatch. I guess I was looking at the OP's code in the OP, and explained about normal polymorphism.


@etrusks

Q1 Just add a Wave function to the Visitor class, like the others.

So I understood that parameter types must match exactly.
Yes.
You can only do this stuff to return types.
It's the type of the argument (not the parameter) that determines what happens in polymorphism. Not sure what you mean by return types?


Q2 Yes, having to cast is a clue to bad design.

Tnx you very much for sticking around for so long time :) I think I finally understand this stuff now! At least for now :D Finally that "Mark as solved" can be checked!

And remember boys and girls, don't write code such as : pubic Visitor :D
n other words, try to push the interface functions as high up the tree as possible.


A added some emphasis this time :+)


As long as a function doesn't reside in a parent class, it is always possible to push it higher. I get your meaning now, but the wording is unfortunate. ;)
Topic archived. No new replies allowed.