zoukankan      html  css  js  c++  java
  • (原创)用c++11打造好用的variant

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

    typedef variant<int,char, double> vt;
    vt v = 1;
    v = '2';
    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 = 10;
    
        cv v =x;
        v = 1;
        v = 1.123;
        v = "";//compile error
        v.Get<int>(); //1
        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,欢迎大家来交流技术。

  • 相关阅读:
    Abp vNext 模块化系统简单介绍
    CLR via C# 笔记 -- 计算限制的异步操作(27)
    CLR via C# 笔记 -- 线程基础(26)
    Redis 入门
    .NET Core 中生成验证码
    CLR via C# 笔记 -- 异常和状态管理(20)
    CLR via C# 笔记 -- 托管堆和垃圾回收(21)
    CLR via C# 笔记 -- 字符、字符串、文本处理(14)
    广州公司黑名单
    总博客教程全导航
  • 原文地址:https://www.cnblogs.com/qicosmos/p/3416432.html
Copyright © 2011-2022 走看看