boost基础——variant的原理及基本用法

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五道口纳什

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值