위 링크에 C++17부터 추가된 Variant와 왜 추가되었는지에 대한 내용이 있는데 간단하게 언급하자면 std::optional로 추가되었어야 할 내용을 boost에서 너무 오래 썼기 때문에 이렇게 오래 끌지 말고 빨리 올려버리자는 이야기를 하고 있다.
데이터 버퍼
템플릿 인자로 주어진 타입들 중 가장 크기가 큰 타입을 저장할 수 있는 충분한 크기의 메모리 공간으로 대체로 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가지 형태로 구분된다고 한다. 사실 구현 방식과 오버헤드, 컴파일러 최적화에 약간의 차이만 있을 뿐, 기본적으로 판별자를 보고 활성화된 Type에 따라 적절한 함수를 호출할 수 있도록 구현되어 있다고 보면 될 것 같다.