"The variant class template is a safe, generic, stack-based discriminated union container, offering a simple solution for manipulating an object from a heterogeneous set of types in a uniform manner. Whereas standard containers such as std::vector may be thought of as "multi-value, single type," variant is "multi-type, single value.
Problem
Many times, during the development of a C++ program, the programmer finds himself in need of manipulating several distinct types in a uniform manner. Indeed, C++ features direct language support for such types through its union keyword:
union { int i; double d; } u;
u.d = 3.14;
u.i = 3; // overwrites u.d (OK: u.d is a POD type)
C++'s union construct, however, is nearly useless in an object-oriented environment. The construct entered the language primarily as a means for preserving compatibility with C, which supports only POD (Plain Old Data) types, and so does not accept types exhibiting non-trivial construction or destruction:
union {
int i;
std::string s; // illegal: std::string is not a POD type!
} u;
union 的成员只能是POD类型。
//<boost程序完全开发指南> #include <boost/variant.hpp> #include <string> #include <iostream> using namespace boost; int main() { typedef variant<int, double, std::string> var_t; var_t v; assert(v.type() == typeid(int)); assert(v.which() == 0); v = "variant demo"; std::cout << *get<std::string>(&v) << std::endl; //使用get()函数取值 try { std::cout << get<double>(v) << std::endl; } catch(bad_get &) { std::cout << "bad_get" << std::endl; } std::cin.get(); }
结果
variant demo
bad_get
const std::type_info& type() const;
返回type_info对象,可判别variant当前的数据类型
“我们也可以使用自由函数get()来获取variant的值:
cout << get<string>(v)”
“但get()函数通常不是最方便的访问方法,”它存在类型不安全的隐患,操作时必须查询variant当前值的类型。
改进版
//<me> #include <boost/variant.hpp> #include <string> #include <iostream> using namespace boost; typedef variant<int, double, std::string> var_t; void var_print(var_t & v) { if(v.type() == typeid(int)) { std::cout << get<int>(v) << std::endl; } else if(v.type() == typeid(double)) { std::cout << get<double>(v) << std::endl; } else if(v.type() == typeid(std::string)) { std::cout << get<std::string>(v) << std::endl; } else { std::cout << "error type" << std::endl; } } int main() { var_t v; v = "variant demo"; var_print(v); v = 3.14; var_print(v); v = 909; var_print(v); std::cin.get(); }
结果:
variant demo
3.14
909
函数var_print()将输入的variant对象输出,它使用了RTTI技术,效率低,并且一旦variant的模板参数发生变化,var_print()也必须改动,其if_else的处理结果也很不优雅。
Variant基于访问者模式提供了模板类static_visitor"
//<.修改代码> #include <boost/variant.hpp> #include <string> #include <iostream> using namespace boost; typedef variant<int, double, std::string> var_t; class print_visitor : public static_visitor<void> { public: void operator()(int i) const { std::cout << "It's an int: " << i << '\n'; } void operator()(std::string s) const { std::cout << "It's a std::string: " << s << '\n'; } void operator()(double d) const { std::cout << "It's a double: " << d << '\n'; } }; int main() { print_visitor v; var_t var; var = "variant demo"; boost::apply_visitor(v, var); var = 3.14; boost::apply_visitor(v, var); var = 909; boost::apply_visitor(v, var); std::cin.get(); }
结果:
It's a std::string: variant demo
It's a double: 3.14
It's an int: 909
二元访问器
“之前的访问器都只能操作一个variant对象,但操作两个variant对象的访问器也允许的,这在某些情况下是有用的,比如对两个variant进行比较。”
"注意,如果调用 get 失败(当 my_first_variant 所含值不是类型 int 时就会发生),会抛出一个类型为 boost::bad_get 的异常。"