본문 바로가기
Language/C++

Variant 초기화의 중요성 bad_variant_access

by W00gie 2022. 6. 3.

Std::Variant는 C++ 17에서 지원하는 공용체이다.

 

공용체란 한 인스턴스에 다양한 타입의 값을 담을 수 있는 구조체를 의미한다. 공용체라면 가장 쉽게 접할 수 있는게 명시적인 Union 일텐데, 현재 Std::Variant는 Value와 함께 저장된 값의 유형을 식별하는 판별자를 관리하기 때문에 공용체를 구현하는데 보다 안정적인 방법으로 선호되고 있다.

(기존의 Union의 경우 타입을 판별하지 않기때문에 논리 오류로 이어질 수 있다는 단점이 존재한다.)

 

* Variant의 이용방법은 다음과 같다.

https://en.cppreference.com/w/cpp/utility/variant

 

std::variant - cppreference.com

template class variant; (since C++17) The class template std::variant represents a type-safe union. An instance of std::variant at any given time either holds a value of one of its alternative types, or in the case of error - no value (this state is hard t

en.cppreference.com

* 구조체의 사이즈는

 Union과 동일하다. 공용체에 속한 Struct중에서 가장 큰 사이즈의 타입을 기준으로 8바이트를 더해 모든 인스턴스들이 동일한 크기를 가진다. 이러한 사항을 메모리풀을 통한 객체할당 방식을 이용할 때 유의할 것.

 

	struct A{
		int a;

	};
	struct B{
		long long b;
		long long c;
	};
	A a;
	B b;

	cout << "size of a:" << sizeof(a) << endl;
	cout << "size of b:" << sizeof(b) << endl;
	
	variant<struct A, struct B> v, w;
	v = a; // v contains int
	
	cout << "size of variant v:" << sizeof(v) << endl;

 

 

Union과 달리 기본적으로 variant는 내부 타입사이즈 + 8바이트가 추가된다

 

* 항상 초기화가 필요하다.

 최근 오랜만에 포스팅을 결심하게 된 이유이다. 앞서 말했듯이 Variant는 기존 Union과 달리 값의 유형을 식별하는 판별자를 관리한다. 이러한 판별자(Access Type)는 초기화와 동시에 액세스 타입을 설정하게 되는데, 이러한 초기화 과정을 거치지 않거나, 설정된 판별자와 다른 타입의 값을 사용할 경우, 해당 Variant에서 명시하는 bad_Variant_Access를 마주하게 된다. 해당 Vairant의 판별자가 논리오류를 방지하는 안전장치 역할을 수행하는데, 필자와 같이 모던 C++에 능숙하지 않은 사용자에게는 한 차례 걸림돌이 된다 할 수 있다. 

해당 형식에서 어긋난 get<int>부분에서 bad_variant_access가 발생함을 확인할 수 있다. 이는 variant를 초기화 하지 않고 사용하는 과정에서도 발생할 수 있다.  (값을 들고 있지 않은 경우는 std::monostate를 활용하면 표현이 가능하지만 아직 필요성을 느끼진 못했다)

필자의 경우 union 의 공용체들이 공통적으로 wchar_t* 변수를 가져 초기화 없이 copy_n을 사용하다 오류를 경험했다.문자열 구조체의 경우에도 variant에 값을 대입하기 이전에는 '{}' 빈 초기화 리스트를 이용해서라도 초기화를 거쳐야 한다.