The Visitor Pattern encapsulates an operation executed on an object hierarchy as an object and enables it to define new operations without changing the object hierarchy.
The Visitor Pattern from the book “Design Patterns: Elements of Reusable Object-Oriented Software” is legendary for two reasons. First, for its complicatedness, and second, for a technique called double dispatch. Double dispatch describes the process of choosing the member function based on the object and the function arguments. Of course, the complicatedness of the Visitor Pattern is mainly due to the fact that double dispatch is not natively supported in C++, such as in Eiffel.
Let me first write about the Visitor Pattern before I discuss single dispatch and double dispatch.
- Encapsulates an operation executed on an object hierarchy in an object
- Enables to define new operations without changing the object hierarchy
- Operations should be performed on an object hierarchy
- The operations change frequently
- The object hierarchy is stable
- Defines the
visitoperation on the object structure
- Implements the
- Defines the
acceptoperation that takes a visitor as an argument
- Implements the
I assume this graphic was too simple. The following picture from the Visitor Pattern on Wikipedia provides more insight.
By Fuhrmanator – Using the PlantUML software https://bit.ly/3MDbtCK, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=122709059
This picture gives a very good impression of the Visitor Pattern. Let me use it to explain the structure and the dynamic behavior of the Visitor Pattern.
The Visitor Pattern has two type of hierarchies. The object hierarchy (
CarElement) and the operation hierarchy (
CarElementVisitor). The object hierarchy is pretty stable, but the operation hierarchy may support new operations. Both classes,
CarElementVisitor act as interfaces. This means that each concrete car element
Car must implement the
accept(CarElementVisitor) member function. Accordingly, each concrete operation
CarElementPrintVisitor must implement the four overloads
- Dynamic Behavior
Let’s assume that the operation
CarElementPrintVisitor is applied to the object hierarchy. The job of
CarElementPrintVisitor may be to print the name of the visited car part. First, a car element such as
Engine accepts the visitor (
accept(CarElementVisitor)) and uses the visitor to call back into the operations hierarchy (
visitor.visit(this)) using itself as an argument. This ensures that the
visit(Engine) overload on the
CarElementPrintVisitor is called. Visiting the
Car is special. A
Car consists of various car elements. Consequently,
Car‘s accept member function delegates the accept call to all its car parts.
Here is the crucial observation about the Visitor. It depends on two objects, which operation is performed: the visitor and the visited object.
The following example literally translated the previous picture into code.
At the beginning of the
main function, all parts of the car are created. Afterward, the engine and the car accept the
carElementPrintVisitor (lines 1 and 2). In lines (3) and (4), both objects are accepted by the
CarElement (line 5) and
CarElementVisitor (line 5) are the abstract base classes of the object hierarchy and the operation hierarchy. According to the picture, the concrete car elements and visitors are created. The car is the most interesting car element because it holds its car elements in a
std::vector<Element*> (line 7).
Finally, here is the output of the program:
The Visitor Pattern is probably the design pattern of the book “Design Patterns: Elements of Reusable Object-Oriented Software” which has the highest pattern density.
- The stable object hierarchy typically applies the Composite Pattern.
- The Iterator Pattern is often used to iterate through the object hierarchy.
- New elements can be created with a creational pattern, such as Factory Method or Prototype.
Pros and Cons
- A new operation (visitor) can be easily added to the operations hierarchy
- An operation is encapsulated in one visitor
- You can build a state while traversing the object hierarchy
- Modifying the object hierarchy with a new visited object
- You have to add or remove the visited object (
VisitedObject) from the object hierarchy
- You have to extend the interface of the visitor and add or remove the
visit(VisitObject)member function to each concrete visitor
- You have to add or remove the visited object (
The complicatedness of the Visitor Pattern is mainly for one reason: double dispatch
Single Dispatch and Double Dispatch
Before I write about double dispatch, let me write about single dispatch, commonly known as virtual function calls.
In a single dispatch, the object decides which member function is called. To get virtuality in C++, you need two ingredients. An indirection such as a pointer or a reference and a virtual member function.
Ball* ballPointer = &hBall (line 1) has two types. The static type (
Ball*) and the dynamic type (
Handball*), return by the address of the operator
&. Because of the virtuality of the member function getName, the member function call is looked up at the run time. Consequentially, a dynamic dispatch happens, and the member function
getName is called. A similar argumentation holds for the reference used in line (2).
Here is the output of the program:
Now, let’s analyze the double dispatch used in the Visitor Pattern.
In double dispatch, it depends on two objects, which operation is performed.
This means, in the concrete case of the program
visitor.cpp, that the visitor and visited object manage together to call the appropriate member function.
To make it concrete: What happens in the call
car.accept(carElementDoVisitor) (line 4)? For simplicity, here is the member function
- The member function
acceptof car iterates through all elements and calls
elemis a pointer and
accepta virtual function => dynamic dispatch
- Finally, the visitor calls
visiton itself, using the visited element as an argument:
visitor.visit(*this).Consequentially, the appropriate overload of the visitor is called => static dispatch
Double dispatch, in the case of the visitor, is a ping/pong game between the element (car element) and the visitor. The car element applies a dynamic dispatch (override), and the visitor a static dispatch (overload).
The Template Method is a behavioral design pattern that defines a template of an algorithm. In C++, we use a special variant of it: Non-Virtual Interface (NVI). I will present the Template Method in my next post.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, moon, and Philipp Lenk.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
|My special thanks to Embarcadero|
|My special thanks to PVS-Studio|
|My special thanks to Tipi.build|
|My special thanks to Take Up Code|
|My special thanks to SHAVEDYAKS|
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
- Embedded Programmierung mit modernem C++ 12.12.2023 – 14.12.2023 (Präsenzschulung, Termingarantie)
Standard Seminars (English/German)
Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.
- C++ – The Core Language
- C++ – The Standard Library
- C++ – Compact
- C++11 and C++14
- Concurrency with Modern C++
- Design Pattern and Architectural Pattern with C++
- Embedded Programming with Modern C++
- Generic Programming (Templates) with C++
- Clean Code with Modern C++
- Phone: +49 7472 917441
- Mobil:: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,