#35. [모던 C++ STL] variant(C++17)
- (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으로 개체를 관리합니다.
- 기본 생성시에는 0번째 타입의 기본 생성값으로 초기화 합니다.
holds_alternative()
으로 주어진 타입이 관리되고 있는지 검사합니다.index()
로 현재 관리되는 타입의 인덱스를 알 수 있습니다.get<인덱스>()
와get<타입>()
으로 값에 접근할 수 있습니다. 만약 해당 값이 없으면bad_variant_access
예외가 발생합니다.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_exception
은 false
입니다.
1
2
std::variant<std::monostate, int, std::string> var{};
EXPECT_TRUE(var.valueless_by_exception() == false);
visit()
예를들어 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);
}
댓글남기기