C++17 표준 라이브러리의 std::variant 소개
C++17 표준 라이브러리에 추가된 std::variant를 소개합니다.
저번 글에 이어 "핵심 C++ 표준 라이브러리" 부록을 방출합니다. 이번에는 std::variant
입니다. 부록에 실린 것과 본질적으로 같은 내용이지만, 링크와 주석이 하나 추가되었습니다.
형식에 안전한 공용체, std::variant
std::any
가 형식에 안전한 void*
라면, std::variant
는 형식에 안전한 공용체(union)라 할 수 있다. 필요한 헤더는 <variant>
이다. 다음은 variant
의 형식 안전성을 보여주는 예이다.
void f()
{
variant<int, double, string> v = 123;
cout << v << endl; // 123
cout << std::get<int>(v) << endl; // 명시적으로 int를 요청.
// 앞의 행과 같은 결과를 낸다.
cout <<std::get<0>(v) << endl; // 첫 번째 형식(int)의 값을 요청.
// 역시 같은 결과를 낸다.
auto f = std::get<float>(v); // (1) 컴파일 오류
auto cond = std::get<string>(v); // (2) 실행 시점 예외 발생
}
이 예제에서 v
는 int
나 double
, bool
을 담을 수 있는 variant
객체인데, (1)은 엉뚱하게 float
값을 요구했다. 이런 오류는 컴파일러가 잡아낼 수 있으므로 컴파일 오류가 발생한다. (2)는 v가 가질 수 있는 세 형식 중 하나인 string
을 요구했으므로 컴파일 오류는 발생하지 않지만, 대신 실행 시점에서 std::bad_variant_access
라는 예외가 발생한다. 이는 v
가 현재 가지고 있는 값, 즉 마지막으로 설정된 값의 형식(int
)이 요청된 형식(string
)과 일치하지 않기 때문이다. 둘 다 union
을 사용할 때 프로그래머들이 흔히 하는 실수인데, variant
는 이런 실수들을 컴파일 시점에서 방지하거나 실행 시점에서 보고해준다. 이런 측면 때문에 variant
를 “형식에 안전한 공용체”라고 부른다. 또한, (1)과 같은 컴파일 시점 점검 능력은 std::any
에 비한 장점이기도 하다.
앞의 예제의 (2)에서 보듯이 variant
를 사용할 때에는 “이 variant
객체에 어떤 형식의 값을 담을 수 있는가?”도 중요하지만 “현재 이 variant
객체에 어떤 형식의 값이 담겨 있는가?”도 중요하다. 이와 관련해서 variant
는 다음과 같은 멤버·비멤버 인터페이스들을 제공한다. 다음 예를 보자.
void g()
{
variant<int, double, bool> v = 123;
// (1)
auto pv = std::get_if<bool>(&v); // pv는 nullptr
// (2)
auto cond = std::holds_alternative<int>(v) // cond는 true
// (3)
auto i = v.index(); // i는 0
}
std::get
과는 달리std::get_if
는 포인터를 받으며, 형식 불일치 시 예외를 던지는 대신 널 포인터를 돌려준다.std::holds_alternative<T>(v)
는 현재 v 객체의 형식이T
인지의 여부를 뜻하는bool
값을 돌려준다.- 멤버 변수
index
는 현재 형식의 색인을 돌려준다. 지금 예에서int
가 0,double
이 1,bool
이 2이다. 이 색인은std::get<i>(v)
형태로 활용할 수 있다. 공용체는 형식 안전성이 떨어질 뿐만 아니라, 소위 POD(plain old data) 형식들만 담을 수 있다. 반면 variant는 첫 예제(f(x)
)의variant<int, double, string>
에서 보듯이 POD가 아닌 본격적인 클래스도 담을 수 있다. 이 덕분에 성격이 크게 다른 형식들을 하나의variant
객체로 공유할 수 있는데, 이러한 능력은 소위 ‘방문자 패턴(Visitor pattern)’의 구현에 도움이 된다. 실제로, 이를 위해variant
는 아예std::visit
이라는 비멤버 함수를 제공한다.[1]
-
이 부분은 variant의 활용에 중요하지만 지면 제한 때문에 간단하게만 언급했는데, 언제
std::variant
와 방문자 패턴, 그리고 '꼬리표 달린 공용체(tagged union)'에 관해 보충 글을 써보겠습니다. ↩