首页 > 编程笔记 > C++笔记 阅读:6

C++ std::variant的用法(附带实例)

C++ 中,联合体(union)是一种特殊的类型,无论何时,它都持有其数据成员之一的值。

与常规类的不同,联合体不能有基类,也不能派生子类,还不能包含虚函数(这无论如何都没有意义)。联合体主要用于定义相同数据的不同表示,但是,联合体仅适用于 POD(Plain Old Data)类型。如果联合体包含非 POD 类型的值,那么这些成员需要显式构造(使用 placement new),并需要显式销毁,这很麻烦且容易出错。

在 C++17 中,类型安全联合体以标准库类模板 std::variant 的形式可用。

类模板 std::variant 是基于 boost::variant 设计的,在 <variant> 头文件中可用。如果你熟悉 boost::variant 并在代码中使用过它,那么可以轻松地迁移代码来使用 std::variant 类模板。

C++ std::variant使用方式

我们可以使用以下操作处理 std::variant:
1) 要修改存储的值,可以使用成员函数 emplace() 或 swap():
struct foo
{
    int value;
    explicit foo(int const i) : value(i) {}
};

std::variant<int, std::string, foo> v = 42; // holds int
v.emplace<foo>(42); // holds foo

2) 要读取存储的值,可以使用非成员函数 std::get() 或 std::get_if():
std::variant<int, double, std::string> v = 42;
auto i1 = std::get<int>(v);
auto i2 = std::get<0>(v);

try
{
    auto f = std::get<double>(v);
}
catch (std::bad_variant_access const & e)
{
    std::cout << e.what() << '\n'; // Unexpected index
}

3) 要存储值,可以使用构造函数或将值直接赋给 variant 对象:
std::variant<int, double, std::string> v;
v = 42; // v contains int 42
v = 42.0; // v contains double 42.0
v = "42"; // v contains string "42"

4) 要检查存储的备选项,可以使用成员函数 index():
std::variant<int, double, std::string> v = 42;
static_assert(std::variant_size_v<decltype(v)> == 3);
std::cout << "index = " << v.index() << '\n';

v = 42.0;
std::cout << "index = " << v.index() << '\n';

v = "42";
std::cout << "index = " << v.index() << '\n';

5) 要检查是否包含备选项,可以使用非成员函数 std::holds_alternative():
std::variant<int, double, std::string> v = 42;
std::cout << "int? " << std::boolalpha << std::holds_alternative<int>(v) << '\n'; // int? true

v = "42";
std::cout << "int? " << std::boolalpha << std::holds_alternative<int>(v) << '\n'; // int? false

6) 要定义第一个备选项非默认可构造的 variant,可以使用 std::monostate 作为第一个备选项(在本例中,foo 与前面使用的类是同一个):
std::variant<std::monostate, foo, int> v;
v = 42; // v contains int 42
std::cout << std::get<int>(v) << '\n';
v = foo{ 42 }; // v contains foo(42)
std::cout << std::get<foo>(v).value << '\n';

7) 要处理 variant 的存储值并根据备选项类型执行某些操作,可以使用 std::visit():
std::variant<int, double, std::string> v = 42;
std::visit([](auto&& arg) { std::cout << arg << '\n'; }, v);

C++ std::variant的工作原理

std::variant 是一个类模板,它是类型安全的联合体,无论何时都持有一个可能的备选项。但是,在一些罕见的情况下,变量 variant 对象可能不存储任何值。

std::variant 有一个名为 valueless_by_exception() 的成员函数,如果没有值,则它返回 true,这只在初始化时出现异常才有可能发生(因此而得名)。

std::variant 对象的大小与其最大的备选项一样大,variant 对象不存储额外的数据,存储的值在对象本身的内存表示中分配。

一个 variant 可以保存同一类型的多个备选项,也可以同时保存不同的常量限定版本。它不能保存 void 类型,也不能保存 array 数组和引用类型。除此之外,第一个备选项必须始终是默认可构造的,因为就像有区别的联合体一样,variant 默认使用其第一个备选项初始化。如果第一个备选项不是默认可构造类型,那么 variant 必须使用 std::monostate 作为第一个可选项,这是一个空类型,它使 variant 默认可以构造。

可以在编译时获取 variant 的大小(即定义的备选项的数量),以及由其从零开始的索引指定的备选项的类型。另外,还可以在运行时使用成员函数 index() 获取当前持有的备选项的索引。

相关文章