variant类似于union,它能代表定义的多种类型,允许将不同类型的值赋给它。它的具体类型是在初始化赋值时确定。boost中的variant的基本用法:

typedef variant<int,char, double> vt;
vt v = ;
v = '';
v = 12.32;

  用variant一个好处是可以擦除类型,不同类型的值都统一成一个variant,虽然这个variant只能存放已定义的类型,但这在很多时候已经够用了。 取值的时候,通过get<T>(v)来获取真实值。然而,当T类型与v的类型不匹配时,会抛出一个bad_cast的异常来。boost的variant抛出的异常往往没有更多的信息,不知道到底是哪个类型转换失败,导致发生异常调试时很不方便。因此,就考虑用c++11去实现一个vairiant, 这个variant可以很容易知道取值时,是什么类型转换失败了。

打造variant需要解决的问题:

第一,要在内部定义一个char缓冲区。

  缓冲区用来存放variant的值,这个值是variant定义的多种类型中的某种类型的值,因此,这个缓冲区要足够大,能够存放类型最大(sizeof(Type))的值才可以,这个缓冲区的大小还必须在编译期计算出来。因此需要首先要解决的是variant值存放的缓冲区定义的问题。

第二,要解决赋值的问题。

  将值赋给vairiant时,需要将该值的类型ID记录下来,以便在后面根据类型取值。将值保存到内部缓冲区时,还需要用palcement new在缓冲区创建对象。另外,还要解决一个问题,就是赋值时需要检查variant中已定义的类型中是否含有该类型,如果没有则编译不通过,以保证赋值是合法的。

第三,要解决取值的问题。

  通过类型取值时,要判断类型是否匹配,如果不匹配,将详情打印出来,方便调试。

打造variant的关键技术:

1.找出最大的typesize

第一个问题中需要解决的问题是如何找出多种类型中,size最大的那个类型的size。看看如何从多种类型中找出最大类型的size。

template<typename T, typename... Args>
struct MaxType : std::integral_constant<int,
(sizeof(T)>MaxType<Args...>::value ? sizeof(T) : MaxType<Args...>::value) > {}; template<typename T>
struct MaxType<T> : std::integral_constant<int, sizeof(T) >{};

通过这个MaxType就可以在编译期获取类型中最大的maxsize了:MaxType<Types...>::value。

2.类型检查和缓冲区中创建对象

第二个问题中需要解决两个问题,1.检查赋值的类型是否在已定义的类型中;2.在缓冲区中创建对象及析构;

先看看如何判断类型列表中是否含有某种类型:

template < typename T, typename... List >
struct Contains : std::true_type {}; template < typename T, typename Head, typename... Rest >
struct Contains<T, Head, Rest...>
: std::conditional< std::is_same<T, Head>::value, std::true_type, Contains<T, Rest...>>::type{}; template < typename T >
struct Contains<T> : std::false_type{};

通过bool值Contains<T, Types>::vaule就可以判断是否含有某种类型。

再看看如何在缓冲区中创建对象。

通过placement new在该缓冲区上创建对象,new(data) T(value);其中data表示一个char缓冲区,T表示某种类型。在缓冲区上创建的对象还必须通过~T去析构,因此还需要一个析构vairiant的帮助类:

template<typename... Args>
struct VariantHelper; template<typename T, typename... Args>
struct VariantHelper<T, Args...> {
inline static void Destroy(type_index id, void * data)
{
if (id == type_index(typeid(T)))
((T*) (data))->~T();
else
VariantHelper<Args...>::Destroy(id, data);
}
}; template<> struct VariantHelper<> {
inline static void Destroy(type_index id, void * data) { }
};

通过VariantHelper::Destroy函数就可以析构variant了。

3.取值问题

第三个问题中需要解决取值问题,如果发生异常,就打印出详细信息。这个就比较简单了,看后面的实现代码就行了。

c++11中完整的variant是如何实现的:

#include <typeindex>
#include <iostream>
#include <type_traits>
using namespace std; template<typename T, typename... Args>
struct MaxType : std::integral_constant<int,
(sizeof(T)>MaxType<Args...>::value ? sizeof(T) : MaxType<Args...>::value) > {}; template<typename T>
struct MaxType<T> : std::integral_constant<int, sizeof(T) >{}; template < typename T, typename... List >
struct Contains : std::true_type {}; template < typename T, typename Head, typename... Rest >
struct Contains<T, Head, Rest...>
: std::conditional< std::is_same<T, Head>::value, std::true_type, Contains<T, Rest...>>::type{}; template < typename T >
struct Contains<T> : std::false_type{}; template<typename... Args>
struct VariantHelper; template<typename T, typename... Args>
struct VariantHelper<T, Args...> {
inline static void Destroy(type_index id, void * data)
{
if (id == type_index(typeid(T)))
((T*) (data))->~T();
else
VariantHelper<Args...>::Destroy(id, data);
}
}; template<> struct VariantHelper<> {
inline static void Destroy(type_index id, void * data) { }
}; template<typename... Types>
class Variant
{
typedef VariantHelper<Types...> Helper_t;
public: Variant(void) :m_typeIndex(typeid(void))
{
} ~Variant()
{
Helper_t::Destroy(m_typeIndex, &m_data);
} template<typename T>
bool Is()
{
return (m_typeIndex == type_index(typeid(T)));
} template<typename T>
T& Get()
{
if (!Is<T>())
{
cout << typeid(T).name() << " is not defined. " << "current type is " << m_typeIndex.name() << endl;
throw std::bad_cast();
} return *(T*) (&m_data);
} template <class T,
class = typename std::enable_if<Contains<typename std::remove_reference<T>::type, Types...>::value>::type>
Variant(T&& value) : m_typeIndex(type_index(typeid(void)))
{
Helper_t::Destroy(m_typeIndex, &m_data);
typedef typename std::remove_reference<T>::type U;
new(m_data) U(std::forward<T>(value));
     m_typeIndex =type_index(typeid(T));
  } 

template<typename F>
void Visit(F&& f)
{
  using T = typename function_traits<F>::arg<0>::type;
  if (Is<T>())
    f(Get<T>());
}

template<typename F, typename... Rest>
void Visit(F&& f, Rest&&... rest)
{
  using T = typename function_traits<F>::arg<0>::type;
  if (Is<T>())
    Visit(std::forward<F>(f));
  else
    Visit(std::forward<Rest>(rest)...);
}

private: char m_data[MaxType<Types...>::value]; 
std::type_index m_typeIndex;
};

测试代码:

void TestVariant()
{
typedef Variant<int, char, double> cv;
int x = ; cv v =x;
v = ;
v = 1.123;
v = "";//compile error
v.Get<int>(); //
v.Get<double>(); //1.23
v.Get<short>(); //exception: short is not defined. current type is int
v.Is<int>();//true
}

总结:c++11实现的variant在用法上和boost.variant保持一致,但实现更简洁,50行代码搞定。而且还能在抛出异常时提示详细信息,方便调试。

c++11 boost技术交流群:296561497,欢迎大家来交流技术。

(原创)用c++11打造好用的variant的更多相关文章

  1. (原创)用c++11打造好用的variant(更新)

    关于variant的实现参考我前面的博文,不过这第一个版本还不够完善,主要有这几个问题: 内部的缓冲区是原始的char[],没有考虑内存对齐: 没有visit功能. 没有考虑赋值构造函数的问题,存在隐 ...

  2. (原创)用c++11打造好用的any

    上一篇博文用c++11实现了variant,有童鞋说何不把any也实现一把,我正有此意,它的兄弟variant已经实现了,any也顺便打包实现了吧.其实boost.any已经挺好了,就是转换异常时,看 ...

  3. (原创)用c++11打造类似于python的range

    python中的range函数表示一个连续的有序序列,range使用起来很方便,因为在定义时就隐含了初始化过程,因为只需要给begin()和end()或者仅仅一个end(),就能表示一个连续的序列.还 ...

  4. 【原创】C++11:左值和右值(深度分析)

    ——原创,引用请附带博客地址 2019-12-06 23:42:18 这篇文章分析的还是不行,先暂时放在这以后再更新. 本篇比较长,需要耐心阅读 以一个实际问题开始分析 class Sub{} Sub ...

  5. 原创:goldengate从11.2升级到12.1.2

    goldengate从11.2升级到12.1.2 1.停止抽取进程 GGSCI (001.oracle.drs.dc.com) 286> stop EXTSJ01 2. 停止投递和复制进程 等待 ...

  6. 用c++11打造类似于python的range

    python中的range函数表示一个连续的有序序列,range使用起来很方便,因为在定义时就隐含了初始化过程,因为只需要给begin()和end()或者仅仅一个end(),就能表示一个连续的序列.还 ...

  7. [原创]EF架构随心所欲打造属于你自己的DbModel

    前言 我们都知道EF可以生成Dbmodel,系统生成的Model有时候并不是我们想要的,如何我们要生成自己的Model,那么久需要我们手动的去修改T4模版,T4是对“Text Template Tra ...

  8. (原创)c++11中 function/lamda的链式调用

    关于链式调用,比较典型的例子是c#中的linq,不过c#中的linq还只是一些特定函数的链式调用.c++中的链式调用更少见因为实现起来比较复杂.c++11支持了lamda和function,在一些延迟 ...

  9. (原创)C++11改进我们的程序之move和完美转发

    本次要讲的是右值引用相关的几个函数:std::move, std::forward和成员的emplace_back,通过这些函数我们可以避免不必要的拷贝,提高程序性能.move是将对象的状态或者所有权 ...

随机推荐

  1. (原)torch7中指定可见的GPU

    转载请注明出处: http://www.cnblogs.com/darkknightzh/p/7418694.html 参考网址: https://gitter.im/torch/torch7/arc ...

  2. 像python一样运行js的__main__

    在测试时,使用单文件进行简单测试既简洁又清晰.js其实也是可以做到的 if (require && require.main == module) { console.log(&quo ...

  3. 【LeetCode】215. Kth Largest Element in an Array (2 solutions)

    Kth Largest Element in an Array Find the kth largest element in an unsorted array. Note that it is t ...

  4. 利用Apache配置http expires值提高网站性能

    HTTP头中有个expires参数,设置一个未来的时间,在这时间以前,浏览器会先从cache读取,如果没有再从服务器中读取.对于像图片,css,script等静态内容,只需发一次http reques ...

  5. fedora国内源常见配置

    yum install yum-fastestmirror3.rpmfusion源 rpm -ivh http://download1.rpmfusion.org/free/fedora/rpmfus ...

  6. 使用ant优化android项目编译速度,提高工作效率

    1.Android项目编译周期长,编译项目命令取消困难 2.在进行Android项目的编译的同时,Eclipse锁定工作区不能进行修改操作 3.在只进行资源文件的修改时,Eclipse对资源文件的修改 ...

  7. 解决servlet-api包冲突问题(maven)

    问题描述:本人的项目是用Maven管理,而且用到了servlet3.0的技术,但是项目中用到servlet3.0的地方,总提示找不到类中的方法.很奇怪,在网上找到好多解决办法,综合一下终于解决了.现将 ...

  8. 【struts2】<package>的配置

    <package>元素可以把逻辑上相关的一组Action.Result.Intercepter等元素封装起来,形成一个独立的模块,package可以继承其他的package,也可以作为父包 ...

  9. MongoDB学习笔记(7)--- 条件操作符

    描述 条件操作符用于比较两个表达式并从mongoDB集合中获取数据. 在本章节中,我们将讨论如何在MongoDB中使用条件操作符. MongoDB中条件操作符有: (>) 大于 - $gt (& ...

  10. win7下 go语言开发环境搭建(64bit)

    Go 是一个开源的编程语言,它能让构造简单.可靠且高效的软件变得容易. Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全.支持并行进程 ...