3 분 소요

  • (C++17~) variant가 추가되어 타입이 다른 여러 데이터들을 동일한 메모리 공간에서 쉽게 관리할 수 있습니다.

개요

C++11에서 추가된 무제한 공용체는 많은 제한이 풀려 타입이 다른 데이터를 공용체로 다룰 수 있는 자유도가 높아졌으나, 위치 지정 생성이나 소멸자를 수동으로 처리해야 하기 때문에 많은 불편함이 있었습니다.

C++17 부터는 variant가 추가되어 타입이 다른 여러 데이터들을 동일한 메모리 공간에서 쉽게 관리할 수 있습니다. any의 경우는 런타임에 동적으로 캐스팅하여 관리하는 타입을 유연하게 변경할 수 있지만, variant는 컴파일 타임에 지정한 타입들중 하나로만 사용할 수 있습니다.

항목 내용
= (C++17~) 복사 대입 또는 이동 대입합니다.
index() (C++17~) 현재 관리되는 타입의 인덱스를 리턴합니다.
valueless_by_exception() (C++17~) variant가 유효한지 검사합니다.
emplace() (C++17~) 내부 개체를 생성해서 전달하는 것이 아니라, 내부 개체의 생성자 인수들을 전달하면 variant내에서 내부 개체를 직접 생성합니다.
swap() (C++17~) 바꿔치기 합니다.
==, != (~C++17) 관리하는 개체를 비교합니다.
<, <=, >, >= (C++17~)
<=> (C++20~)
관리하는 개체를 비교합니다.
visit() (C++17~) variant가 관리하는 값으로 함수자를 호출합니다.
holds_alternative() (C++17~) 주어진 타입이 관리되고 있는지 검사합니다.
get() (C++17~) 주어진 인덱스나 타입의 값을 리턴합니다. 값이 없으면, bad_variant_access 예외가 발생합니다.
get_if() (C++17~) 주어진 타입의 값을 리턴합니다. 값이 없으면, nullptr을 리턴합니다.
monostate (C++17~) 초기값 없이 variant를 생성합니다.
bad_variant_access (C++17~) variant에서 주어진 타입이 없을때 bad_variant_access 예외가 발생합니다.
variant_size, variant_size_t (C++17~) (작성중)
variant_alternative, variant_alternative_t (C++17~) (작성중)
variant_npos (C++17~) variant 인덱스가 잘못된 경우를 나타냅니다.-1입니다.

다음 예에서는 variant를 이용하여 int 또는 string으로 개체를 관리합니다.

  1. 기본 생성시에는 0번째 타입의 기본 생성값으로 초기화 합니다.
  2. holds_alternative()으로 주어진 타입이 관리되고 있는지 검사합니다.
  3. index() 로 현재 관리되는 타입의 인덱스를 알 수 있습니다.
  4. get<인덱스>()get<타입>()으로 값에 접근할 수 있습니다. 만약 해당 값이 없으면 bad_variant_access 예외가 발생합니다.
  5. get_if()는 값이 없으면 nullptr을 리턴합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 기본 생성하면 0번째 타입의 기본 생성값으로 초기화 합니다.
std::variant<int, std::string> var{};
EXPECT_TRUE(std::holds_alternative<int>(var) == true);
EXPECT_TRUE(var.index() == 0 && std::get<0>(var) == 0 && std::get<int>(var) == 0);

// 정수 값을 입력하여 인덱스는 0입니다.
var = 1;
EXPECT_TRUE(std::holds_alternative<int>(var) == true);
EXPECT_TRUE(var.index() == 0 && std::get<0>(var) == 1 && std::get<int>(var) == 1);

// 문자열을 입력하여 인덱스는 1입니다.
std::string str{"Hello"};
var = str;
EXPECT_TRUE(std::holds_alternative<std::string>(var) == true);
EXPECT_TRUE(var.index() == 1 && std::get<1>(var) == "Hello" && std::get<std::string>(var) == "Hello");

// 값이 없으면 예외를 발생합니다.
try {
    var = 1;
    std::get<std::string>(var);
}
catch (std::bad_variant_access&) {
    std::cout << "bad_variant_access" << std::endl;
}

// 값이 없으면 널을 리턴합니다.
EXPECT_TRUE(std::get_if<std::string>(&var) == nullptr);    

variant의 타입 제약

variant는 동일한 타입을 여러개 사용할 수 없습니다.

1
2
std::variant<int, int> var{};
var = 1; // (X) 컴파일 오류. 타입이 동일하면 사용할 수 없습니다.

valueless_by_exception()

variant는 생성시에 0번째 타입의 개체의 기본 생성자로 초기화 해주고, 별도로 초기화 하는 함수도 없기 때문에, 일반적인 상황에선 항상 유효한 개체를 갖게 되는데요,

생성자나 대입 과정에서 예외가 발생하면 variant가 유효하지 않은 상태가 됩니다. valueless_by_exception()는 이런 경우 variant의 유효성을 검사합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class T {
public:
    T() {}
    T(const T&) {throw std::exception();}
};

std::variant<int, T> var{10}; 
EXPECT_TRUE(var.valueless_by_exception() == false);

try {
    var = T{}; // 내부적으로 복사 생성되면서 예외를 발생합니다.
}
catch (std::exception&) {}

EXPECT_TRUE(var.valueless_by_exception() == true); // 유효하지 않습니다.

monostate

variant는 기본 생성시에 0번째 타입으로 초기화 합니다. 만약 아무값도 없는 상태로 생성하려면 특별히 monostate로 생성합니다.

유효한 값은 아니지만 예외가 발생한 것은 아니므로 valueless_by_exceptionfalse 입니다.

1
2
std::variant<std::monostate, int, std::string> var{};
EXPECT_TRUE(var.valueless_by_exception() == false); 

visit()

variant가 관리하는 값으로 함수자를 호출합니다.

예를들어 variant를 저장한 vector의 각 요소에 Func()을 호출하고 싶다면 다음과 같이 holds_alternative()variant가 주어진 타입의 값을 저장했는지 확인 후 Func()에 해당 값을 전달해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::vector<std::variant<int, std::string>> v{
    10, // v[0]
    20, // v[1]
    "hello" // v[2]
};

// 각 타입에 따라 검사한후 타입에 맞게 get해야 합니다.
for (auto& var : v) {
    if (std::holds_alternative<int>(var)) {
        Func(std::get<int>(var));            
    }
    if (std::holds_alternative<std::string>(var)) {
        Func(std::get<std::string>(var));            
    }
}   

visit()를 이용하면 상기 과정을 은닉하여 다음과 같이 호출할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
std::vector<std::variant<int, std::string>> v{
    10, // v[0]
    20, // v[1]
    "hello" // v[2]
};

// 타입에 따라 검사하는 코드를 visit() 함수 안에 은닉합니다.
for (auto& element : v) {
    std::visit([](auto&& val){Func(val);}, element);
}  

태그:

카테고리:

업데이트:

댓글남기기