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. 高密度WIFI部署要点

    1. 划分AP组,分组带宽控制 根据区域的人数密集程度划分不同的AP组,并进行优化策略调整,分组分权限进行带宽控制,以确保单用户的2.4G带宽不低于1M,5G用户不低于2M2. 相邻AP错开信道 超高 ...

  2. Android(java)同步方法synchronized

    synchronized 是java语言keyword.当它用来修饰一个方法或者一个代码块的时候,可以保证在同一时刻最多仅仅有一个线程运行该段代码. synchronized keyword,它包含两 ...

  3. 自己动手做——邮件客户端FrankMail

    一.预备知识 二.需求分析 三.编码 四.交付 软件界面: 发送结果: --EOF--

  4. 【JS】移动端 好用的分享插件 soshm.js

    参考链接:https://www.cnblogs.com/milo-wjh/p/6796082.html 对于qq内置浏览器分享功能处理:https://www.cnblogs.com/xuzheng ...

  5. CentOS下nodejs最简单的安装方法

    1. 下载编译好的文件 我的系统是centos7,进入要存放下载资源的目录,个人建议存放在/usr/local/src/目录下.然后执行安装命令: wget http://nodejs.org/dis ...

  6. swift类型转换之Could not cast value of type xxx to xxx错误的一种特殊情况记录

    之前swift项目打包成Framework静态库,提供给OC项目套入使用时,有时会抱这样一个错误: 这个错误发生的概率比较随机,有时会,有时不会,而且这句话在swift中的使用,是做model类型强制 ...

  7. JSP弹出对话框方式小结

    转自:http://blog.csdn.net/ithomer/article/details/8033002 该博主(创业)另一博客地址: http://blog.mimvp.com JSP 网页在 ...

  8. windows下如何生成gitlab ssh公钥

    1.查看是否已经有了ssh密钥:cd ~/.ssh如果没有密钥则不会有此文件夹,有则备份删除2.生存密钥: $ ssh-keygen -t rsa -C “你的邮箱”按3个回车,密码为空. Your ...

  9. HDU 1710 Binary Tree Traversals (二叉树遍历)

    Binary Tree Traversals Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/O ...

  10. C语言中 不定义结构体变量求成员大小

    所谓的求成员大小, 是求成员在该结构体中 用 sizeof(结构体名.结构体成员名) 求来的. 很多时候我们需要知道一个结构体成员中的某个成员的大小, 但是我们又不需要定义该结构体类型的变量(定义的话 ...