[2026] C++ Visitor Pattern | 방문자 패턴 가이드

[2026] C++ Visitor Pattern | 방문자 패턴 가이드

이 글의 핵심

C++ Visitor Pattern: 방문자 패턴 가이드. Visitor Pattern이란?·:variant + std::visit (C++17).

Visitor Pattern이란?

연산을 객체 구조 밖으로 빼는 흐름은 행동 패턴 시리즈에서 Observer·Command와 함께 묶여 있습니다. 더블 디스패치 패턴 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
// 전방 선언
class Circle;
class Rectangle;
// Visitor
class ShapeVisitor {
public:
    virtual void visit(Circle& c) = 0;
    virtual void visit(Rectangle& r) = 0;
    virtual ~ShapeVisitor() = default;
};
// Shape
class Shape {
public:
    virtual void accept(ShapeVisitor& v) = 0;
    virtual ~Shape() = default;
};
class Circle : public Shape {
public:
    Circle(double r) : radius(r) {}
    void accept(ShapeVisitor& v) override { v.visit(*this); }
    double getRadius() const { return radius; }
private:
    double radius;
};
class Rectangle : public Shape {
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    void accept(ShapeVisitor& v) override { v.visit(*this); }
    double getWidth() const { return width; }
    double getHeight() const { return height; }
private:
    double width, height;
};
// 구체적 Visitor
class AreaCalculator : public ShapeVisitor {
public:
    void visit(Circle& c) override {
        area = 3.14159 * c.getRadius() * c.getRadius();
    }
    
    void visit(Rectangle& r) override {
        area = r.getWidth() * r.getHeight();
    }
    
    double getArea() const { return area; }
    
private:
    double area = 0;
};
int main() {
    Circle c(5.0);
    Rectangle r(4.0, 6.0);
    
    AreaCalculator calc;
    c.accept(calc);
    std::cout << "Circle area: " << calc.getArea() << '\n';
    
    r.accept(calc);
    std::cout << "Rectangle area: " << calc.getArea() << '\n';
}

std::variant + std::visit (C++17)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <variant>
#include <iostream>
struct Circle {
    double radius;
};
struct Rectangle {
    double width, height;
};
using Shape = std::variant<Circle, Rectangle>;
// Visitor (함수 객체)
struct AreaCalculator {
    double operator()(const Circle& c) const {
        return 3.14159 * c.radius * c.radius;
    }
    
    double operator()(const Rectangle& r) const {
        return r.width * r.height;
    }
};
int main() {
    Shape s1 = Circle{5.0};
    Shape s2 = Rectangle{4.0, 6.0};
    
    double area1 = std::visit(AreaCalculator{}, s1);
    double area2 = std::visit(AreaCalculator{}, s2);
    
    std::cout << "Circle: " << area1 << '\n';
    std::cout << "Rectangle: " << area2 << '\n';
}

실전 예시

예시 1: 여러 Visitor

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <variant>
#include <iostream>
#include <string>
struct Circle { double radius; };
struct Rectangle { double width, height; };
struct Triangle { double base, height; };
using Shape = std::variant<Circle, Rectangle, Triangle>;
// 면적
struct AreaVisitor {
    double operator()(const Circle& c) const {
        return 3.14159 * c.radius * c.radius;
    }
    double operator()(const Rectangle& r) const {
        return r.width * r.height;
    }
    double operator()(const Triangle& t) const {
        return 0.5 * t.base * t.height;
    }
};
// 출력
struct PrintVisitor {
    void operator()(const Circle& c) const {
        std::cout << "Circle(r=" << c.radius << ")\n";
    }
    void operator()(const Rectangle& r) const {
        std::cout << "Rectangle(w=" << r.width << ", h=" << r.height << ")\n";
    }
    void operator()(const Triangle& t) const {
        std::cout << "Triangle(b=" << t.base << ", h=" << t.height << ")\n";
    }
};
int main() {
    Shape s = Circle{5.0};
    
    double area = std::visit(AreaVisitor{}, s);
    std::cout << "Area: " << area << '\n';
    
    std::visit(PrintVisitor{}, s);
}

예시 2: Lambda Visitor

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <variant>
#include <iostream>
// overloaded helper
template<typename....Ts>
struct overloaded : Ts....{
    using Ts::operator()...;
};
template<typename....Ts>
overloaded(Ts...) -> overloaded<Ts...>;
struct Circle { double radius; };
struct Rectangle { double width, height; };
using Shape = std::variant<Circle, Rectangle>;
int main() {
    Shape s = Circle{5.0};
    
    std::visit(overloaded{
         {
            std::cout << "Circle: " << c.radius << '\n';
        },
         {
            std::cout << "Rectangle: " << r.width << "x" << r.height << '\n';
        }
    }, s);
}

예시 3: 상태 변경

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <variant>
#include <iostream>
struct Idle {};
struct Running { int progress; };
struct Completed { int result; };
using State = std::variant<Idle, Running, Completed>;
struct StateHandler {
    void operator()(Idle&) {
        std::cout << "State: Idle\n";
    }
    
    void operator()(Running& r) {
        std::cout << "State: Running (" << r.progress << "%)\n";
        r.progress += 10;
    }
    
    void operator()(Completed& c) {
        std::cout << "State: Completed (result=" << c.result << ")\n";
    }
};
int main() {
    State state = Running{50};
    
    std::visit(StateHandler{}, state);
    std::visit(StateHandler{}, state);
}

예시 4: AST 순회

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <variant>
#include <memory>
#include <iostream>
struct Number {
    int value;
};
struct BinaryOp {
    char op;
    std::unique_ptr<struct Expr> left;
    std::unique_ptr<struct Expr> right;
};
using ExprVariant = std::variant<Number, BinaryOp>;
struct Expr {
    ExprVariant data;
};
struct Evaluator {
    int operator()(const Number& n) const {
        return n.value;
    }
    
    int operator()(const BinaryOp& op) const {
        int l = std::visit(*this, op.left->data);
        int r = std::visit(*this, op.right->data);
        
        switch (op.op) {
        case '+': return l + r;
        case '-': return l - r;
        case '*': return l * r;
        case '/': return l / r;
        default: return 0;
        }
    }
};

전통 vs Modern

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 전통 (가상 함수)
class Visitor {
    virtual void visit(TypeA&) = 0;
    virtual void visit(TypeB&) = 0;
};
class Shape {
    virtual void accept(Visitor&) = 0;
};
// Modern (variant + visit)
using Shape = std::variant<TypeA, TypeB>;
struct Visitor {
    void operator()(TypeA&) { /* ....*/ }
    void operator()(TypeB&) { /* ....*/ }
};
std::visit(Visitor{}, shape);

자주 발생하는 문제

문제 1: 타입 추가

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 전통: 모든 Visitor 수정
class Visitor {
    virtual void visit(Circle&) = 0;
    virtual void visit(Rectangle&) = 0;
    // Triangle 추가 시 모든 Visitor 수정
};
// Modern: variant만 수정
using Shape = std::variant<Circle, Rectangle, Triangle>;

문제 2: 반환 값

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ 반환 값
struct AreaVisitor {
    double operator()(const Circle& c) const {
        return 3.14159 * c.radius * c.radius;
    }
};
double area = std::visit(AreaVisitor{}, shape);

문제 3: 상태

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Visitor에 상태
struct Counter {
    int count = 0;
    
    void operator()(const Circle&) { ++count; }
    void operator()(const Rectangle&) { ++count; }
};
Counter counter;
for (const auto& shape : shapes) {
    std::visit(counter, shape);
}

문제 4: 순환 의존

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 전방 선언 + unique_ptr
struct Expr;
struct BinaryOp {
    std::unique_ptr<Expr> left;
    std::unique_ptr<Expr> right;
};
struct Expr {
    std::variant<Number, BinaryOp> data;
};

사용 시기

  • 전통 Visitor: 타입 고정, 다형성 필요
  • std::variant + std::visit: 타입 제한적, 성능 중요, 모던 C++

FAQ

Q1: Visitor Pattern?

A: 더블 디스패치 패턴.

Q2: 용도?

A: 타입별 처리.

Q3: std::visit?

A: C++17 variant 방문.

Q4: 장점?

A: 타입 안전, 확장성.

Q5: 단점?

A: 타입 추가 시 수정 필요.

Q6: 학습 리소스는?

A:

  • “Design Patterns”
  • “C++17 STL”
  • cppreference.com

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3