C++ std::variant的用法(附带实例)
在 C++ 中,联合体(union)是一种特殊的类型,无论何时,它都持有其数据成员之一的值。
与常规类的不同,联合体不能有基类,也不能派生子类,还不能包含虚函数(这无论如何都没有意义)。联合体主要用于定义相同数据的不同表示,但是,联合体仅适用于 POD(Plain Old Data)类型。如果联合体包含非 POD 类型的值,那么这些成员需要显式构造(使用 placement new),并需要显式销毁,这很麻烦且容易出错。
在 C++17 中,类型安全联合体以标准库类模板 std::variant 的形式可用。
类模板 std::variant 是基于 boost::variant 设计的,在
1) 要修改存储的值,可以使用成员函数 emplace() 或 swap():
2) 要读取存储的值,可以使用非成员函数 std::get() 或 std::get_if():
3) 要存储值,可以使用构造函数或将值直接赋给 variant 对象:
4) 要检查存储的备选项,可以使用成员函数 index():
5) 要检查是否包含备选项,可以使用非成员函数 std::holds_alternative():
6) 要定义第一个备选项非默认可构造的 variant,可以使用 std::monostate 作为第一个备选项(在本例中,foo 与前面使用的类是同一个):
7) 要处理 variant 的存储值并根据备选项类型执行某些操作,可以使用 std::visit():
std::variant 有一个名为 valueless_by_exception() 的成员函数,如果没有值,则它返回 true,这只在初始化时出现异常才有可能发生(因此而得名)。
std::variant 对象的大小与其最大的备选项一样大,variant 对象不存储额外的数据,存储的值在对象本身的内存表示中分配。
一个 variant 可以保存同一类型的多个备选项,也可以同时保存不同的常量限定版本。它不能保存 void 类型,也不能保存 array 数组和引用类型。除此之外,第一个备选项必须始终是默认可构造的,因为就像有区别的联合体一样,variant 默认使用其第一个备选项初始化。如果第一个备选项不是默认可构造类型,那么 variant 必须使用 std::monostate 作为第一个可选项,这是一个空类型,它使 variant 默认可以构造。
可以在编译时获取 variant 的大小(即定义的备选项的数量),以及由其从零开始的索引指定的备选项的类型。另外,还可以在运行时使用成员函数 index() 获取当前持有的备选项的索引。
与常规类的不同,联合体不能有基类,也不能派生子类,还不能包含虚函数(这无论如何都没有意义)。联合体主要用于定义相同数据的不同表示,但是,联合体仅适用于 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() 获取当前持有的备选项的索引。