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.”
联合(union)的问题
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; int d;} u;
u.d = 3.14;
u.i = 3; // overwrites u.d
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;
关于 POD以及Aggregates类型的详尽讨论请见 C++ Aggregate 与 POD(Plain Old Data)的解释。
基本用法(Basic Usage)
boost::variant<int, std::string> v;
默认地,variant
的默认构造函数通过模板中的第一个类型(严格地说,为bounded type
,本例为int
)进行构造,所以,初始状态下,v
被初始化为int(0)
。
std::cout << v.type().name() << std::endl;
// int
std::cout << v << std::endl;
// 0
如果模板中的第一个类型不存在默认构造,将会报错,哪怕后续类型存在默认构造:
class A
{
public:
A(int){}
};
int main(int, char**)
{
boost::variant<A, int> v;
// 错误 1 error C2512: “A”: 没有合适的默认构造函数可用
return 0;
}
如果这种默认的处理方式不是我们期待的,或者如上例所示,第一个模板类型不存在默认的构造函数,variant
对象可直接通过模板中的任意类型的构造函数的方式得以构造;
boost::variant<A, int> v1 = 5;
boost::variant<A, int> v2 = A(5);
类似地,一个variant
对象也可通过模板中的任意类型的构造函数进行赋值,
int main(int, char**)
{
boost::variant<int, std::string> v;
v = "hello";
return 0;
}
共有两种方式获取 variant
对象中的内容:
- apply_visitor,安全且强大
- get<T>,十分方便
std::string& str = boost::get<std::string>(v);
str += " world! ";
std::cout << str << std::endl;
// hello world!
get<T>
方法存在一个显著的缺陷,也即,如果一个函数接受variant<int, std::string>
,我们无法知道传递进来的variant
对象,是int
类型还是std::string
类型。此时我们便需要进行多一步的判断,
void times_two(boost::variant<int, std::string>& v)
{
if (int& a = boost::get<int>(v))
a *= 2;
if (std::string* s = boost::get<std::string>(&v))
s += s;
}
然而这样的代码虽然能够工作,却十分的脆弱,如果不十分小心将会产生一些只在运行期才会出现的逻辑错误。例如,考虑如果我们希望扩展 times_two
操纵一个具有额外类型的variant
类型变量。显然,此时我们至少需要修改函数声明:
void times_two(boost::variant<int, std::string, std::complex<double>>& v)
{
// 同上...
}
如果我们不对函数体进行任何修改,如果传递进来的是持有std::complex<double>
类型的variant
对象,函数将不做任何动作,也不会报错。对本例而言,我们很容易知道如何修改,
if (std::complex<double>* c = boost::get<std::complex<double>>(&v))
*c *= 2;
在一些更为复杂的程序中,将会花费可观的时间来识别和定位 bug
的位置。
因此,在真实世界应用中,对variant
类型对象内容的获取需要一个比get
方法更为鲁棒的获取机制,这正是安全且强大的apply_visitor
:
class times_two_visitor :public boost::static_visitor<>
{
public:
void operator()(int& i) const
{
i *= 2;
}
void operator()(std::string& s) const
{
s += s;
}
};
boost::apply_visitor(times_two_visitor(), v);
为了增强鲁棒性,还可写出如下的 generic 版本:
class times_two_generic :public boost::static_visitor<>
{
public:
template<typename T>
void operator()(T& operand) const
{
operand += opearand;
}
}
int main(int, char**)
{
std::vector<boost::variant<int, std::string>> vec;
vec.push_back(5);
vec.push_back("hello");
std::for_each(vec.begin(), vec.end(), boost::apply_visitor(times_two_visitor()));
for (auto& elem: vec)
std::cout << elem << " ";
std::cout << std::endl;
return 0;
}
References
[1] Boost.Variant