BehavioralC++verifiedVerified
Visitor Pattern in C++
Lets you add new operations to an object structure without modifying the objects themselves, by separating the algorithm from the object structure it operates on.
How to Implement the Visitor Pattern in C++
1Step 1: Define the Visitor interface
class ShapeVisitor {
public:
virtual ~ShapeVisitor() = default;
virtual void visitCircle(const Circle& c) = 0;
virtual void visitRectangle(const Rectangle& r) = 0;
};2Step 2: Element interface with accept method
class Shape {
public:
virtual ~Shape() = default;
virtual void accept(ShapeVisitor& visitor) const = 0;
};
class Circle : public Shape {
double radius_;
public:
explicit Circle(double r) : radius_(r) {}
double radius() const { return radius_; }
void accept(ShapeVisitor& visitor) const override { visitor.visitCircle(*this); }
};
class Rectangle : public Shape {
double w_, h_;
public:
Rectangle(double w, double h) : w_(w), h_(h) {}
double width() const { return w_; }
double height() const { return h_; }
void accept(ShapeVisitor& visitor) const override { visitor.visitRectangle(*this); }
};3Step 3: Concrete visitor: area calculator
class AreaCalculator : public ShapeVisitor {
double total_ = 0;
public:
void visitCircle(const Circle& c) override {
total_ += 3.14159 * c.radius() * c.radius();
}
void visitRectangle(const Rectangle& r) override {
total_ += r.width() * r.height();
}
double total() const { return total_; }
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(3.0, 4.0));
AreaCalculator calc;
for (const auto& s : shapes) s->accept(calc);
std::cout << "Total area: " << calc.total() << "\n";
}#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <variant>
#include <format>
#include <cmath>
#include <functional>
#include <numeric>
// [step] Modern C++ visitor using std::variant + std::visit
struct Circle {
double radius;
std::string name() const { return std::format("Circle(r={})", radius); }
};
struct Rectangle {
double width, height;
std::string name() const { return std::format("Rect({}x{})", width, height); }
};
struct Triangle {
double base, height;
std::string name() const { return std::format("Tri(b={},h={})", base, height); }
};
using Shape = std::variant<Circle, Rectangle, Triangle>;
// [step] Visitor functions using overloaded lambdas
template <typename... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template <typename... Ts> overloaded(Ts...) -> overloaded<Ts...>;
// [step] Area visitor
auto areaVisitor = overloaded{
[](const Circle& c) { return M_PI * c.radius * c.radius; },
[](const Rectangle& r) { return r.width * r.height; },
[](const Triangle& t) { return 0.5 * t.base * t.height; },
};
// Perimeter visitor
auto perimeterVisitor = overloaded{
[](const Circle& c) { return 2.0 * M_PI * c.radius; },
[](const Rectangle& r) { return 2.0 * (r.width + r.height); },
[](const Triangle& t) {
double hyp = std::sqrt(t.base * t.base + t.height * t.height);
return t.base + t.height + hyp;
},
};
// Name visitor
auto nameVisitor = overloaded{
[](const Circle& c) { return c.name(); },
[](const Rectangle& r) { return r.name(); },
[](const Triangle& t) { return t.name(); },
};
// [step] Generic visitation utilities
template <typename Visitor>
double sumOver(const std::vector<Shape>& shapes, Visitor visitor) {
double total = 0;
for (const auto& shape : shapes)
total += std::visit(visitor, shape);
return total;
}
// [step] Serialize visitor that produces structured output
auto serializeVisitor = overloaded{
[](const Circle& c) -> std::string {
return std::format(R"({{"type":"circle","radius":{},"area":{:.2f}}})",
c.radius, M_PI * c.radius * c.radius);
},
[](const Rectangle& r) -> std::string {
return std::format(R"({{"type":"rect","w":{},"h":{},"area":{:.2f}}})",
r.width, r.height, r.width * r.height);
},
[](const Triangle& t) -> std::string {
return std::format(R"({{"type":"tri","b":{},"h":{},"area":{:.2f}}})",
t.base, t.height, 0.5 * t.base * t.height);
},
};
// [step] Demonstrate multiple visitors on the same data
int main() {
std::vector<Shape> shapes = {
Circle{5.0},
Rectangle{4.0, 6.0},
Triangle{3.0, 4.0},
};
std::cout << "Shapes:\n";
for (const auto& shape : shapes) {
auto name = std::visit(nameVisitor, shape);
auto area = std::visit(areaVisitor, shape);
auto peri = std::visit(perimeterVisitor, shape);
std::cout << std::format(" {} -> area={:.2f}, perimeter={:.2f}\n",
name, area, peri);
}
std::cout << std::format("\nTotal area: {:.2f}\n", sumOver(shapes, areaVisitor));
std::cout << "\nSerialized:\n";
for (const auto& shape : shapes)
std::cout << " " << std::visit(serializeVisitor, shape) << "\n";
}Visitor Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Visitor Pattern in the Real World
“Think of a tax auditor visiting different types of businesses—a restaurant, a law firm, a retail shop. The auditor (visitor) knows exactly what to examine at each type of business and applies the appropriate inspection procedure. The businesses (elements) simply let the auditor in; they don't change their own operations to accommodate the audit.”