The Visitor Pattern

Contents[Show]

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.

 

BehavioralPattern

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.

Visitor Pattern

Purpose

  • Encapsulates an operation executed on an object hierarchy in an object
  • Enables to define new operations without changing the object hierarchy

Use Case

  • Operations should be performed on an object hierarchy
  • The operations change frequently
  • The object hierarchy is stable

Structure

Visitor

Visitor

  • Defines the visit operation on the object structure

ConcreteVistor

  • Implements the visit operation

Element

  • Defines the accept operation that takes a visitor as an argument

ConcreteElement

  • Implements the accept operation

 

I assume this graphic was too simple. The following picture from the Visitor Pattern on Wikipedia provides more insight.

VisitorWiki

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.

  • Structure

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, CarElement and CarElementVisitor act as interfaces. This means that each concrete car element Wheel, Engine, Body, and Car must implement the accept(CarElementVisitor) member function. Accordingly, each concrete operation CarElementDoVisitor and CarElementPrintVisitor must implement the four overloads visit(Wheel), visit(Engine), visit(Body), and visit(Car).

  • 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.

Example

The following example literally translated the previous picture into code.

// visitor.cpp

#include <iostream>
#include <string>
#include <vector>

class CarElementVisitor;

class CarElement {                                     // (5)       
 public:
    virtual void accept(CarElementVisitor& visitor) const = 0;
virtual ~CarElement() = default; }; class Body; class Car; class Engine; class Wheel; class CarElementVisitor { // (6) public: virtual void visit(Body body) const = 0; virtual void visit(Car car) const = 0; virtual void visit(Engine engine) const = 0; virtual void visit(Wheel wheel) const = 0;
virtual ~CarElementVisitor() = default; }; class Wheel: public CarElement { public: Wheel(const std::string& n): name(n) { } void accept(CarElementVisitor& visitor) const override { visitor.visit(*this); } std::string getName() const { return name; } private: std::string name; }; class Body: public CarElement { public: void accept(CarElementVisitor& visitor) const override { visitor.visit(*this); } }; class Engine: public CarElement { public: void accept(CarElementVisitor& visitor) const override { visitor.visit(*this); } }; class Car: public CarElement { public: Car(std::initializer_list<CarElement*> carElements ): elements{carElements} {} void accept(CarElementVisitor& visitor) const override { for (auto elem : elements) { elem->accept(visitor); } visitor.visit(*this); } private: std::vector<CarElement*> elements; // (7) }; class CarElementDoVisitor: public CarElementVisitor { void visit(Body body) const override { std::cout << "Moving my body" << '\n'; } void visit(Car car) const override { std::cout << "Starting my car" << '\n'; } void visit(Wheel wheel) const override { std::cout << "Kicking my " << wheel.getName() << " wheel" << '\n'; } void visit(Engine engine) const override { std::cout << "Starting my engine" << '\n'; } }; class CarElementPrintVisitor: public CarElementVisitor { void visit(Body body) const override { std::cout << "Visiting body" << '\n'; } void visit(Car car) const override { std::cout << "Visiting car" << '\n'; } void visit(Wheel wheel) const override { std::cout << "Visiting " << wheel.getName() << " wheel" << '\n'; } void visit(Engine engine) const override { std::cout << "Visiting engine" << '\n'; } }; int main() { std::cout << '\n'; Wheel wheelFrontLeft("front left"); Wheel wheelFrontRight("front right"); Wheel wheelBackLeft("back left"); Wheel wheelBackRight("back right"); Body body; Engine engine; Car car {&wheelFrontLeft, &wheelFrontRight, &wheelBackLeft, &wheelBackRight, &body, &engine}; CarElementPrintVisitor carElementPrintVisitor; engine.accept(carElementPrintVisitor); // (1) car.accept(carElementPrintVisitor); // (2) std::cout << '\n'; CarElementDoVisitor carElementDoVisitor; engine.accept(carElementDoVisitor); // (3) car.accept(carElementDoVisitor); // (4) std::cout << '\n'; }

 

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 carElementDoVisitor. 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:

visitor

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.

Pros and Cons

Pros

  • 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

Cons

  • Modifying the object hierarchy with a new visited object VisitedObject is hard
    • 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

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Stay informed about my mentoring programs. Subscribe for the news.

 

 

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.

Single Dispatch

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.

 

// singleDispatch.cpp

#include <iostream>

class Ball {
 public:
    virtual std::string getName() const = 0;
virtual ~Ball() = default; }; class HandBall: public Ball { std::string getName() const override { return "HandBall"; } }; int main() { std::cout << '\n'; HandBall hBall; Ball* ballPointer = &hBall; // (1) std::cout << "ballPointer->getName(): " << ballPointer->getName() << '\n'; Ball& ballReference = hBall; // (2) std::cout << "ballReference.getName(): " << ballReference.getName() << '\n'; std::cout << '\n'; }

 

The expression 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:

singleDispatch

 

Now, let's analyze the double dispatch used in the Visitor Pattern.

Double Dispatch

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 accept.

void accept(CarElementVisitor& visitor) const override {
    for (auto elem : elements) {
        elem->accept(visitor);
    }
    visitor.visit(*this);
}

 

  1. The member function accept of car iterates through all elements and calls elem->accept(visitor) on them: elem is a pointer and accept a virtual function => dynamic dispatch
  2. Finally, the visitor calls visit on 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).

What's Next?

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, Animus24, 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, and Ben Atakora.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, Ralf Abramowitsch, John Nebel, Mipko, and Alicja Kaminska.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

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++

New

  • Clean Code with Modern C++
  • C++20

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

 

 

 

 

Mentoring

Stay Informed about my Mentoring

 

English Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 1269

Yesterday 6867

Week 15174

Month 1269

All 11296683

Currently are 146 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments