std::variant & std::visit

TraceofLight·2025년 7월 28일
0

TIL

목록 보기
8/8
post-thumbnail

기타 정보

관련 문서

위 링크에 C++17부터 추가된 Variant와 왜 추가되었는지에 대한 내용이 있는데 간단하게 언급하자면 std::optional로 추가되었어야 할 내용을 boost에서 너무 오래 썼기 때문에 이렇게 오래 끌지 말고 빨리 올려버리자는 이야기를 하고 있다.

내부 구조

1. Variant

  • 데이터 버퍼
    템플릿 인자로 주어진 타입들 중 가장 크기가 큰 타입을 저장할 수 있는 충분한 크기의 메모리 공간으로 대체로 Union으로 구현되어 있음

  • 판별자
    현재 데이터 버퍼에 어떤 타입의 객체가 저장되어 있는지를 나타내는 인덱스 또는 식별자로 이 판별자를 사용하는 것을 통해 std::variant는 타입 안전성을 보장할 수 있음

충분한 크기의 메모리 공간을 가지는 것을 통해서 variant 타입은 아래와 같은 행동이 가능하다.


	struct Tracer
	{
		std::string name;

		Tracer(const std::string& n) : name(n)
		{
			std::cout << "  [+] '" << name << "' Tracer created (Constructor)\n";
		}

		// Copy Constructor
		Tracer(const Tracer& other) : name(other.name)
		{
			std::cout << "  [*] '" << name << "' Tracer copy-constructed (Copy Constructor)\n";
		}

		~Tracer()
		{
			std::cout << "  [-] '" << name << "' Tracer destroyed (Destructor)\n";
		}
	};


	// 1. Initialize the variant with a Tracer type.
	std::cout << "1. Initializing variant with Tracer(\"Apple\").\n";
	std::variant<int, Tracer> var = Tracer("Apple");
	std::cout << "   Variant now holds Tracer(\"Apple\").\n\n";

	// 2. Assign a value of a different type (int).
	std::cout << "2. Assigning integer 100 to the variant.\n";
	var = 100;
	std::cout << "   Variant now holds integer 100.\n\n";

	// 3. Assign another Tracer type value again.
	std::cout << "3. Assigning Tracer(\"Banana\") to the variant.\n";
	var = Tracer("Banana");
	std::cout << "   Variant now holds Tracer(\"Banana\").\n\n";

	std::cout << "4. main function is about to end.\n";
	return 0;

이와 같은 코드를 실행시키면 아래와 같은 결과를 얻는다.

1. Initializing variant with Tracer("Apple").
  [+] 'Apple' Tracer created (Constructor)
  [*] 'Apple' Tracer copy-constructed (Copy Constructor)
  [-] 'Apple' Tracer destroyed (Destructor)
   Variant now holds Tracer("Apple").

2. Assigning integer 100 to the variant.
  [-] 'Apple' Tracer destroyed (Destructor)
   Variant now holds integer 100.

3. Assigning Tracer("Banana") to the variant.
  [+] 'Banana' Tracer created (Constructor)
  [*] 'Banana' Tracer copy-constructed (Copy Constructor)
  [-] 'Banana' Tracer destroyed (Destructor)
   Variant now holds Tracer("Banana").

4. main function is about to end.
  [-] 'Banana' Tracer destroyed (Destructor)

기존에 있던 값에 대한 소멸자가 먼저 호출되고, 스택에 임시 객체를 생성한 뒤 복사 생성한 값을 본인 메모리 버퍼로 가져오는 순서를 확인할 수 있다.

따라서, 가장 큰 크기의 클래스를 저장할 공간을 내부적으로 유니온으로 보유하게 되면 variant type이 어떤 게 들어오던지 문제 없이 사용이 가능하게 만들 수 있다!

2. Visit

이건 라이브러리마다 다르지만,

  • 함수 포인터 테이블
  • Switch 문

크게 2가지 형태로 구분된다고 한다. 사실 구현 방식과 오버헤드, 컴파일러 최적화에 약간의 차이만 있을 뿐, 기본적으로 판별자를 보고 활성화된 Type에 따라 적절한 함수를 호출할 수 있도록 구현되어 있다고 보면 될 것 같다.

profile
24시간은 부족한 게 맞다

0개의 댓글