14.4. Boost.Variant
Boost.Variant 和 Boost.Any 之间的不同点在于 Boost.Any 可以被视为任意的类型, 而 Boost.Variant 只能被视为固定数量的类型。 让我们来看下面这个例子。
- #include <boost/variant.hpp>
- int main()
- {
- boost::variant<double, char> v;
- v = 3.14;
- v = 'A';
- }
Boost.Variant 为我们提供了一个定义在 boost/variant.hpp
中的类: boost::variant
。 既然 boost::variant
是一个模板, 你必须要指定至少一个参数。 Variant 所存储的数据类型就由这些参数来指定。 上面的例子就给 v 指定了 double
类型和 char
类型。 注意, 一旦你将一个 int
值赋给了 v, 你的代码将不会编译通过。
当然, 上面的例子也可以用一个 union
类型来实现, 但是与 union 不同的是: boost::variant
可以储存像 std::string
这样的 class 类型的数据。
- #include <boost/variant.hpp>
- #include <string>
- int main()
- {
- boost::variant<double, char, std::string> v;
- v = 3.14;
- v = 'A';
- v = "Hello, world!";
- }
要访问 v 中的数据, 你可以使用独立的 boost::get()
函数。
- #include <boost/variant.hpp>
- #include <string>
- #include <iostream>
- int main()
- {
- boost::variant<double, char, std::string> v;
- v = 3.14;
- std::cout << boost::get<double>(v) << std::endl;
- v = 'A';
- std::cout << boost::get<char>(v) << std::endl;
- v = "Hello, world!";
- std::cout << boost::get<std::string>(v) << std::endl;
- }
boost::get()
需要传入一个模板参数来指明你需要返回的数据类型。 若是指定了一个非法的类型, 你会遇到一个运行时而不是编译期的错误。
所有 boost::variant
类型的值都可以被直接写入标准输入流这样的流中, 这可以在一定程度上让你避开运行时错误的风险。
- #include <boost/variant.hpp>
- #include <string>
- #include <iostream>
- int main()
- {
- boost::variant<double, char, std::string> v;
- v = 3.14;
- std::cout << v << std::endl;
- v = 'A';
- std::cout << v << std::endl;
- v = "Hello, world!";
- std::cout << v << std::endl;
- }
想要分别处理各种不同类型的数据, Boost.Variant 为我们提供了一个名为 boost::apply_visitor()
的函数。
- #include <boost/variant.hpp>
- #include <boost/any.hpp>
- #include <vector>
- #include <string>
- #include <iostream>
- std::vector<boost::any> vector;
- struct output :
- public boost::static_visitor<>
- {
- void operator()(double &d) const
- {
- vector.push_back(d);
- }
- void operator()(char &c) const
- {
- vector.push_back(c);
- }
- void operator()(std::string &s) const
- {
- vector.push_back(s);
- }
- };
- int main()
- {
- boost::variant<double, char, std::string> v;
- v = 3.14;
- boost::apply_visitor(output(), v);
- v = 'A';
- boost::apply_visitor(output(), v);
- v = "Hello, world!";
- boost::apply_visitor(output(), v);
- }
boost::applyvisitor()
第一个参数需要传入一个继承自 boost::static_visitor
类型的对象。 这个类必须要重载 operator()()
运算符来处理 boost::variant
每个可能的类型。 相应的, 例子中的 _v 就重载了三次 operator() 来处理三种可能的类型: double
, char
和 std::string
。
再仔细看代码, 不难发现 boost::static_visitor
是一个模板。 那么,当 operator()()
有返回值的时候, 就必须返回一个模板才行。 如果 operator() 像例子那样没有返回值时, 你就不需要模板了。
boost::apply_visitor()
的第二个参数是一个 boost::variant
类型的值。
在使用时, boost::apply_visitor()
会自动调用跟第二个参数匹配的 operator()()
。 示例程序中的 boost::apply_visitor()
就自动调用了三个不同的 operator 第一个是 double
类型的, 第二个是 char
最后一个是 std::string
。
boost::apply_visitor()
的优点不只是“自动调用匹配的函数”这一点。 更有用的是, boost::apply_visitor()
会确认是否 boost::variant
中的每个可能值都定义了相应的函数。 如果你忘记重载了任何一个函数, 代码都不会编译通过。
当然, 如果对每种类型的操作都是一样的, 你也可以像下面的示例一样使用一个模板来简化你的代码。
- #include <boost/variant.hpp>
- #include <boost/any.hpp>
- #include <vector>
- #include <string>
- #include <iostream>
- std::vector<boost::any> vector;
- struct output :
- public boost::static_visitor<>
- {
- template <typename T>
- void operator()(T &t) const
- {
- vector.push_back(t);
- }
- };
- int main()
- {
- boost::variant<double, char, std::string> v;
- v = 3.14;
- boost::apply_visitor(output(), v);
- v = 'A';
- boost::apply_visitor(output(), v);
- v = "Hello, world!";
- boost::apply_visitor(output(), v);
- }
既然 boost::apply_visitor()
可以在编译期确定代码的正确性, 你就该更多的使用它而不是 boost::get()
。