14.4. Boost.Variant

Boost.Variant 和 Boost.Any 之间的不同点在于 Boost.Any 可以被视为任意的类型, 而 Boost.Variant 只能被视为固定数量的类型。 让我们来看下面这个例子。

  1. #include <boost/variant.hpp>
  2.  
  3. int main()
  4. {
  5. boost::variant<double, char> v;
  6. v = 3.14;
  7. v = 'A';
  8. }

Boost.Variant 为我们提供了一个定义在 boost/variant.hpp 中的类: boost::variant 。 既然 boost::variant 是一个模板, 你必须要指定至少一个参数。 Variant 所存储的数据类型就由这些参数来指定。 上面的例子就给 v 指定了 double 类型和 char 类型。 注意, 一旦你将一个 int 值赋给了 v, 你的代码将不会编译通过。

当然, 上面的例子也可以用一个 union 类型来实现, 但是与 union 不同的是: boost::variant 可以储存像 std::string 这样的 class 类型的数据。

  1. #include <boost/variant.hpp>
  2. #include <string>
  3.  
  4. int main()
  5. {
  6. boost::variant<double, char, std::string> v;
  7. v = 3.14;
  8. v = 'A';
  9. v = "Hello, world!";
  10. }

要访问 v 中的数据, 你可以使用独立的 boost::get() 函数。

  1. #include <boost/variant.hpp>
  2. #include <string>
  3. #include <iostream>
  4.  
  5. int main()
  6. {
  7. boost::variant<double, char, std::string> v;
  8. v = 3.14;
  9. std::cout << boost::get<double>(v) << std::endl;
  10. v = 'A';
  11. std::cout << boost::get<char>(v) << std::endl;
  12. v = "Hello, world!";
  13. std::cout << boost::get<std::string>(v) << std::endl;
  14. }

boost::get() 需要传入一个模板参数来指明你需要返回的数据类型。 若是指定了一个非法的类型, 你会遇到一个运行时而不是编译期的错误。

所有 boost::variant 类型的值都可以被直接写入标准输入流这样的流中, 这可以在一定程度上让你避开运行时错误的风险。

  1. #include <boost/variant.hpp>
  2. #include <string>
  3. #include <iostream>
  4.  
  5. int main()
  6. {
  7. boost::variant<double, char, std::string> v;
  8. v = 3.14;
  9. std::cout << v << std::endl;
  10. v = 'A';
  11. std::cout << v << std::endl;
  12. v = "Hello, world!";
  13. std::cout << v << std::endl;
  14. }

想要分别处理各种不同类型的数据, Boost.Variant 为我们提供了一个名为 boost::apply_visitor() 的函数。

  1. #include <boost/variant.hpp>
  2. #include <boost/any.hpp>
  3. #include <vector>
  4. #include <string>
  5. #include <iostream>
  6.  
  7. std::vector<boost::any> vector;
  8.  
  9. struct output :
  10. public boost::static_visitor<>
  11. {
  12. void operator()(double &d) const
  13. {
  14. vector.push_back(d);
  15. }
  16.  
  17. void operator()(char &c) const
  18. {
  19. vector.push_back(c);
  20. }
  21.  
  22. void operator()(std::string &s) const
  23. {
  24. vector.push_back(s);
  25. }
  26. };
  27.  
  28. int main()
  29. {
  30. boost::variant<double, char, std::string> v;
  31. v = 3.14;
  32. boost::apply_visitor(output(), v);
  33. v = 'A';
  34. boost::apply_visitor(output(), v);
  35. v = "Hello, world!";
  36. boost::apply_visitor(output(), v);
  37. }

boost::applyvisitor() 第一个参数需要传入一个继承自 boost::static_visitor 类型的对象。 这个类必须要重载 operator()() 运算符来处理 boost::variant 每个可能的类型。 相应的, 例子中的 _v 就重载了三次 operator() 来处理三种可能的类型: doublecharstd::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 中的每个可能值都定义了相应的函数。 如果你忘记重载了任何一个函数, 代码都不会编译通过。

当然, 如果对每种类型的操作都是一样的, 你也可以像下面的示例一样使用一个模板来简化你的代码。

  1. #include <boost/variant.hpp>
  2. #include <boost/any.hpp>
  3. #include <vector>
  4. #include <string>
  5. #include <iostream>
  6.  
  7. std::vector<boost::any> vector;
  8.  
  9. struct output :
  10. public boost::static_visitor<>
  11. {
  12. template <typename T>
  13. void operator()(T &t) const
  14. {
  15. vector.push_back(t);
  16. }
  17. };
  18.  
  19. int main()
  20. {
  21. boost::variant<double, char, std::string> v;
  22. v = 3.14;
  23. boost::apply_visitor(output(), v);
  24. v = 'A';
  25. boost::apply_visitor(output(), v);
  26. v = "Hello, world!";
  27. boost::apply_visitor(output(), v);
  28. }

既然 boost::apply_visitor() 可以在编译期确定代码的正确性, 你就该更多的使用它而不是 boost::get()