If you have read the “Gang of Four” book about design patterns or just have been long enough in software development, you will have heard of the Visitor pattern. In its fully object oriented manifestation, this pattern can be rather complex.

Example

Let’s consider a simple parser for a small external DSL, e.g. for some mathematical expressions. Among other things, the parser may have classes to represent all kinds of expressions in an abstract syntax tree (AST).

Those expressions can be numbers, unary operators applied to a subexpression, and binary operators applied to subexpressions. Here is a selection of a few simple AST classes:

class Expression { public: virtual ~Expression() = 0; }; inline Expression::~Expression() = default; using ExpressionPtr = std::unique_ptr<Expression>; class BinaryExpression : public Expression { ExpressionPtr lhs; ExpressionPtr rhs; public: BinaryExpression(ExpressionPtr left, ExpressionPtr right) : lhs(move(left)), rhs(move(right)) { assert(lhs && rhs); } Expression& left() { return *lhs; } Expression& right() { return *rhs; } }; class AddExpression : public BinaryExpression { public: using BinaryExpression::BinaryExpression; }; class MultiplyExpression : public BinaryExpression { public: using BinaryExpression::BinaryExpression; }; class NumberExpression : public Expression { double number; public: NumberExpression(double d) : number(d) {} double getNumber() const { return number; } };

A snippet of that DSL could look like `3 + 4 * 6`. It’s AST could then be created like this:

auto expression = std::make_unique<AddExpression>( std::make_unique<NumberExpression>(3), std::make_unique<MultiplyExpression>( std::make_unique<NumberExpression>(4), std::make_unique<NumberExpression>(6) ) );

Visitor pattern – the object oriented way

This is all pretty straight forward. However, we already see that the `AddExpression` and `MultiplyExpression` are essentially the same, as would be a `SubtractExpression`, `DivideExpression`, `LogicalOrExpression`, `GreaterExpression`, and so on.

Now imagine we would like to work with the AST. There usually are a bunch of different things we could do with it: Print the expression, print or otherwise display the tree structure itself, calculate the result of our expression, and so on.

All those actions are not part of the tree’s behavior. The tree is merely a data structure, and the behavior belongs to an expression printer, a tree display and a calculator.

This is a classical example for the visitor pattern: Whenever you have a hierarchy of classes and a set of actions that do belong to external classes, it is a hint that the visitor pattern should be applied. More so if the classes are less likely to change than the external actions.

The basic idea

The basic idea of the visitor pattern is to have a `Visitor` base class that visits a bunch of objects of the class hierarchy (i.e. the `Expression`s) in question. It calls an `accept` or `acceptVisitor` method on each object.

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

This method in turn is implemented in each class of the hierarchy. Its responsibility is to call back a `visit` method on the visitor specific to the visited object’s class. In our case those could be named `visitAdd`, `visitMultiply`, `visitNumber` etc.

class ExpressionVisitor { public: virtual void visitAdd(AddExpression&) = 0; virtual void visitMultiply(MultiplyExpression&) = 0; virtual void visitNumber(NumberExpression&) = 0; //... }; class AddExpression : public BinaryExpression { //... public: void accept(ExpressionVisitor& visitor) override { visitor.visitAdd(*this); } }; // repeat for all Expression subclasses

Now we can derive a special visitor for each external action from the visitor base class and implement these class specific `visit` methods.

class ExpressionPrinter : public ExpressionVisitor { std::ostream& os; void visitBinaryExpression(BinaryExpression& binExpr, std::string const& infix) { binExpr.left().accept(*this); os << infix; binExpr.right().accept(*this); } public: ExpressionPrinter(std::ostream& ostream) : os(ostream) {} void print(Expression& expr) { expr.accept(*this); os << '

'; } void visitAdd(AddExpression& addExpr) override { visitBinaryExpression(addExpr, " + "); } void visitMultiply(MultiplyExpression& mulExpr) override { visitBinaryExpression(mulExpr, " * "); } void visitNumber(NumberExpression& numExpr) override { os << numExpr.getNumber(); } };

You can see the full code for the current state on this revision of my GitHub repository.

Taking stock

Let’s gather the number of classes and methods we have now: We have one abstract visitor base class and one concrete visitor for each external action. Let’s call that latter number of actions A.

We also have a number of abstract classes for the expression class hierarchy, and one concrete class for each different flavor of expression (Add, Multiply, …) I’ll call the number of concrete expression classes E.

Each concrete expression class has to implement the accept method which is a trivial one-liner – but it has to be done, E times. Each concrete visitor has to implement the visit method for each concrete expression, which makes a total of E × A visit methods.

If we have A different actions that really do different things for each expression flavor, there is no way around the E × A complexity. However, if we look at the expression classes, we have lots of repetition.

Except for the getters and constructors there is only one single function in each expression class that actually does something: The `accept` method.

Conclusion

You see, if we really stick to the book, we come out with an implementation that is rather complex for this otherwise simple example.

Next week I will pick up at this point and show an alternative implementation that does have less impact on the side of the expression classes.