본문 바로가기

Language/C++ Design Pattern

Visitor 패턴 (std::variant)

(C++ 소프트웨어 디자인 책 정리)

 

 앞서 구현한 Visitor 패턴을 std::variant, std::visit을 사용해서 구현해보자.

 

std::variant

std::variant 는 변수를 여러 타입을 선택하여 만든 후 선택한 타입 중 하나만 담을 수 있다.

아래 main() 예시를 보자.

int main()
{
  //0으로 초기화한 'int'를 담고 있는 기본 variant를 생성한다.
  std::variant<int, double, std::string> v{};
  
  v = 42; //'int' 형 에 대입
  v = "Hyojin" // 문자열 string 에 대입
  v = 3.14 // 'double' 대입
  
  int tmp = std::get<int>(v); // 값에 직접 접근한다 (참조형으로 return)
  int pTmp = std::get_if<int>(&v); // 값 pointer로 접근한다. (pointer return)
  
  return 0;
}

 

std::visit

std::visit 은 필요한 타입 디스패치를 수행한다.

아래 예시를 보자

struct Move
{
  void operator()( Circle const& c) const
  { /* 원을 움직이는 논리 구현 */ }
  void operator()( Square const& s) const
  { /* 사각형을 움직이는 논리 구현 */ }
};


int main(void)
{
  using Shape = std::variant<Circle, Square>;
  using Shapes = std::vector<Shape>;
  
  for (auto const& item : Shapes) {
    // execute operator() of move structure is according to shapes
    std::visit(Move{}, item);
  }
}

 

std::visit 에 호출하고자 하는 클래스(연산)를 왼쪽에, 타입(Arg)를 오른쪽에 입력 후 실행하면 연산 클래스의 operator()이 실행 된다.

이렇게 하면 별도의 visitor 클래스를 만들고 상속할 필요 없이 간단히 visitor 패턴을 구현할 수 있다.

 

즉 요지는 만들고자 하는 연산의 구조체(또는 클래스)를 선언 후 해당 operator()를 선언 후 연산구조만 만들면 끝이다!

이전 Circle, Square, Triapezoid 클래스는 별도의 클래스를 상속 받을 필요가 없어졌다!!

 

이전에 구현한 visitor 패턴에서 아래 Move 연산을 구조체로 선언, std::variant, std::visit 사용하여 visitor 패턴을 구현하였다.

/* === Shape.h 파일 === */
// ... 생략 ...
using Shape = std::variant<Circle, Square, Trapezoid>;
// ... 생략 ...
/* === Shape.h END === */

/* === Move.h 파일 === */
// ... 생략 ...
class Move
{
  private:
    double movingX_;
    double movingY_;
  public:
    explicit Move( double x, double y )
      : movingX_(x), movingY_(y) {}

  void operator()( Circle& c) {
    c.setCenter(movingX_, movingY_);
    std::cout << " Circle move x " << movingX_ << " move y " << movingY_ << std::endl;
  }

  void operator()( Square& s) {
    s.setCenter(movingX_, movingY_);
    std::cout << " Square move x " << movingX_ << " move y " << movingY_ << std::endl;
  }

  void operator()( Trapezoid& t) {
    t.setCenter(movingX_, movingY_);
    std::cout << " Trapezoid move x " << movingX_ << " move y " << movingY_ << std::endl;
  }
};
// ... 생략 ...
/* === Move.h END === */

/* === Circle.h === */
// ... 생략 ...
class Circle
{
  private:
    double radius_;
    Point center_{};

  public:
    explicit Circle( double radius ) {
      if (radius < 0) {
        std::cout << "Circle radius is invalid " << std::endl;
      } else {
        std::cout << "Circle radius is valid " << std::endl;
        this->radius_ = radius;
      }
    }

    void draw() const {
      std::cout << " circle drawing~!" << std::endl;
    }

    Point getCenter() { return center_; }

    void setCenter( double x, double y) {
      center_.x = x;
      center_.y = y;
    }

    double getRadius() { return radius_; }
};
/* === Cicle.h END === */

 

아래와 같이 main 함수를 구현하여 visitor 패턴을 완성한다.

int main()
{
  Shapes shapes;

  shapes.emplace_back( Circle{3} );
  shapes.emplace_back( Square{2} );
  shapes.emplace_back( Trapezoid{} );

#if 1
  for (auto& item : shapes) {
    std::visit(Move{1,2}, item);
  }
#endif

  return 0;
}

 

github 코드: https://github.com/KJT9109/designPattern/tree/master/VisitorPattern_with_variant

'Language > C++ Design Pattern' 카테고리의 다른 글

Visitor 패턴  (1) 2025.02.02
DIP 원칙  (0) 2025.01.30
LSP 원칙  (0) 2025.01.30
OCP 원칙  (0) 2025.01.29
ISP 원칙  (0) 2025.01.29