tags:
- Cpp
Standard Variant in C++
Do this at first: Unions in C++
std::variant
: A Type-Safe Union在联合体的小节中,我们谈到由于联合体的特性,每次只能有一个成员处于 active 的活跃状态,而其他的成员会处于 inactive 的非活跃状态(也就是未定义状态)。这就导致了我们对这些未定义数据成员的访问都算是未定义行为。而大多数的编译器并不会阻止我们对这些非活跃成员的访问。
而 std::variant
是一个对 union
的封装,它不允许用户访问那些可能引起未定义行为的非活跃成员。它提供 std::get()
的接口给用户来访问某个类型或索引的值。当用户访问到非活跃的成员时,就会抛出一个异常。我们用下面这个例子说明一下:
#include <iostream>
#include <variant>
int main() {
std::variant<short, int, float, double> U;
U = 7; // assigns 7, which is interpreted as int
std::cout << std::get<short>(U) << std::endl; // not okay, U currently holds int, throw std::bad_variant_access type exception
std::cout << std::get<int>(U) << std::endl; // okay
U = 7.5; // assigns 7.5, which is interpreted as double
std::cout << std::get<double>(U) << std::endl; // okay
std::cout << std::get<float>(U) << std::endl; // not okay
return 0;
}
此外,我们还可以通过 std::get_if()
来获取 std::variant
中存储的值(指针)。和上面的 std::get()
不同的是, std::get_if()
并不会抛出异常,如果类型不匹配或者提供的索引是无效的,它会返回一个 nullptr
。如果类型是匹配的,就会返回指向存储值的指针。
std::variant<short, int> U = 42;
if (int* pval = std::get_if<int>(&U)) {
std::cout << "Value: " << *pval << std::endl;
} else {
std::cout << "Type mismatch or invalid access." << std::endl;
}
为了实现类型安全, std::variant
额外用一个 4 字节的 index
值来存储当前处于活跃状态成员变量的索引值。这也就是为什么当你用 sizeof()
查看大小时,你会发现 std::variant
类型往往比相同结构的 union
大四个字节。
std::variant
实际上就相当于:
namespace std {
template<typename... Types>
class variant {
private:
union Storage {
std::aligned_union_t<0, Types...> data; // aligned storage for the largest type
} storage;
int type_index; // keeps track of the current active type
public:
// Constructors, destructors, assignment operators, and member functions would be implemented here
// Example accessor function
int index() const {
return type_index;
}
};
}
每个值都能够对应着一个 union
里面的类型,通过当前保存的 index
值,std::variant
就能够知道哪个数据成员是活跃的,从而保证了类型安全。我们用 index()
函数来获取当前活跃类型的索引值。
#include <iostream>
#include <variant>
int main() {
std::variant<short, int, float, double> U;
U = static_cast<short>(7); // assigns to short type, which index is 0
std::cout << U.index() << std::endl; // 0
U = static_cast<int>(7); // assigns to short type, which index is 0 1
std::cout << U.index() << std::endl; // 1
U = static_cast<float>(7.5); // assigns to float type, which index is 2
std::cout << U.index() << std::endl; // 2
U = static_cast<double>(7.5); // assigns to double type, which index is 3
std::cout << U.index() << std::endl; // 3
return 0;
}
既然索引对应着唯一的类型,我们实际上也可以直接用 index
值对数据成员进行访问。同样的,如果访问的索引并不是活跃成员的索引,也会抛出一个异常。如:
#include <iostream>
#include <variant>
int main() {
std::variant<short, int, float, double> U = 7; // int type, index = 1
std::cout << std::get<1>(U) << std::endl; // 7
std::cout << std::get<0>(U) << std::endl; // throw std::bad_variant_access
return 0;
}
当抛出异常时,我们可以对其进行捕获并处理。
#include <iostream>
#include <variant>
int main(){
std::variant<short, int> U = 7; // int type, index = 1
try {
std::cout << std::get<short>(U) << std::endl;
} catch (const std::bad_variant_access& ex) {
std::cout << ex.what() << std::endl;
}
return 0;
}