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

      关于variant的实现参考我前面的博文,不过这第一个版本还不够完善,主要有这几个问题:

    1. 内部的缓冲区是原始的char[],没有考虑内存对齐;
    2. 没有visit功能。
    3. 没有考虑赋值构造函数的问题,存在隐患。

      这次将解决以上问题,还将进一步增强variant的功能。增加的功能有:

    1. 通过索引位置获取类型。
    2. 通过类型获取索引位置。

    c++11的内存对齐

      关于内存对齐的问题,将用c++11的std::aligned_storage来代替char[]数组,它的原型是:

    template< std::size_t Len, std::size_t Align = /*default-alignment*/ >
    struct aligned_storage;

      其中Len表示所存储类型的size,Align表示该类型内存对齐的大小,通过sizeof(T)可以获取T的size,通过alignof(T)可以获取T内存对齐大小,所以std::aligned_storage的声明是这样的:std::aligned_storage<sizeof(T), alignof(T)>。alignof是vs2013 ctp中才支持的,如果没有该版本则可以用std::alignment_of来代替,可以通过std::alignment_of<T>::value来获取内存对齐大小。故std::aligned_storage可以这样声明:std::aligned_storage<sizeof(T), std::alignment_of<T>::value>。

      这里要说一下alignof和std::alignment_of的区别,主要区别:

    • std::alignment_of对于数组来说,是获取数组中元素类型内存对齐大小,如果非数组则是类型本身的内存对齐大小,因此使用时要注意这一点。其实std::alignment_of可以由align来实现:
    template<class T>
    struct remove_all_extents { typedef T type;};
     
    template<class T>
    struct remove_all_extents<T[]> {
        typedef typename remove_all_extents<T>::type type;
    };
     
    template<class Tp, std::size_t N>
    struct remove_all_extents<T[N]> {
        typedef typename remove_all_extents<T>::type type;
    
    template< class T >
    struct alignment_of : std::integral_constant<
                              std::size_t,
                              alignof(typename std::remove_all_extents<T>::type)
                           > {};
    • alignof和sizeof有点类似,它可以应用于变长类型,比如alignof(Args)...,而std::alignment_of则不行。

    variant赋值构造函数的问题

      variant如果通过默认赋值函数赋值的话会造成两个variant的缓冲区都是一个,会导致重复析构。variant的赋值函数需要做两件事,第一是借助于赋值的variant的缓冲区取得其实际的类型;第二用赋值的variant种实际的类型构造出当前variant的实际类型。赋值函数的左值和右值版本的实现如下:

        Variant(Variant<Types...>&& old) : m_typeIndex(old.m_typeIndex)
        {
            Helper_t::move(old.m_typeIndex, &old.m_data, &m_data);
        }
    
        Variant(const Variant<Types...>& old) : m_typeIndex(old.m_typeIndex)
        {
            Helper_t::copy(old.m_typeIndex, &old.m_data, &m_data);
        }

    右值版本Helper_t::move的内部是这样的:new (new_v) T(std::move(*reinterpret_cast<T*>(old_v)));

    左值版本Helper_t::copy的内部是这样的:new (new_v) T(*reinterpret_cast<const T*>(old_v));

    右值版本可以直接将原对象move走,左值版本则需要拷贝原来的对象。

    variant的visit功能

      boost.variant中可以使用apply_visitior来访问variant中实际的类型,具体的做法是先创建一个从boost::static_visitor<T>派生的访问者类,这个类中定义了访问variant各个类型的方法,接着将这个访问者对象和vairant对象传到boost::apply_visitor(visitor, it->first)实现vairant的访问。一个简单的例子是这样的:

    //创建一个访问者类,这个类可以访问vairant<int,short,double,std::string>
    struct VariantVisitor : public boost::static_visitor<void>
    {
        void operator() (int a)
        {
            cout << "int" << endl;
        }
    
        void operator() (short val)
        {
            cout << "short" << endl;
        }
    
        void operator() (double val)
        {
            cout << "double" << endl;
        }
    
        void operator() (std::string val)
        {
            cout << "string" << endl;
        }
    
    };
    
    boost::variant<int,short,double,std::string> v = 1;
    boost::apply_visitor(visitor, it->first); //将输出int

      实际上这也是标准的访问者模式的实现,这种方式虽然可以实现对variant内部实际类型的访问,但是有一个缺点是有点繁琐,还需要定义一个函数对象,不够方便。c++11中有了lambda表达式了,是不是可以用lambda表达式来替代函数对象呢?如果直接通过一组lambda表达式来访问实际类型的话,那将是更直观而方便的访问方式,不再需要从boost::static_visitor派生了,也不需要写一堆重载运算符。我希望这样访问vairant的实际类型:

    typedef Variant<int, double, string, int> cv;
    cv v = 10;
    v.Visit([&](double i){cout << i << endl; }, [](short i){cout << i << endl; }, [=](int i){cout << i << endl; },[](string i){cout << i << endl; });
    
    //结果将输出10

    这种方式比boost的访问方式更简洁直观。这个版本中将增加这种内置的访问方式。

    比boost.variant多的更能

      这个版本增加了通过索引获取类型和通过类型获取索引的功能,你可以从variant中获取更多信息,boost.variant中是没有这两个接口的。

    typedef Variant<int, double, string, int> cv;
    cv v = 10;
    cout << typeid(cv::IndexType<1>).name() << endl; //将输出double
    int i = v.GetIndexOf<string>(); //将输出索引位置2

    c++11版本的variant

      下面来看看c++11版本的vairant的具体实现了:

    #include <typeindex>
    #include <iostream>
    #include <type_traits>
    using namespace std;
    
    template <typename T>
    struct function_traits
        : public function_traits<decltype(&T::operator())>
    {};
    // For generic types, directly use the result of the signature of its 'operator()'
    
    template <typename ClassType, typename ReturnType, typename... Args>
    struct function_traits<ReturnType(ClassType::*)(Args...) const>
        // we specialize for pointers to member function
    {
        enum { arity = sizeof...(Args) };
        // arity is the number of arguments.
    
        typedef ReturnType result_type;
    
        template <size_t i>
        struct arg
        {
            typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
            // the i-th argument is equivalent to the i-th tuple element of a tuple
            // composed of those arguments.
        };
    
        typedef std::function<ReturnType(Args...)> FunType;
        typedef std::tuple<Args...> ArgTupleType;
    };
    
    //获取最大的整数
    template <size_t arg, size_t... rest>
    struct IntegerMax;
    
    template <size_t arg>
    struct IntegerMax<arg> : std::integral_constant<size_t, arg>
    {
        //static const size_t value = arg;
        //enum{value = arg};
    };
    
    //获取最大的align
    template <size_t arg1, size_t arg2, size_t... rest>
    struct IntegerMax<arg1, arg2, rest...> : std::integral_constant<size_t, arg1 >= arg2 ? IntegerMax<arg1, rest...>::value :
        IntegerMax<arg2, rest...>::value >
    {
        /*static const size_t value = arg1 >= arg2 ? static_max<arg1, others...>::value :
        static_max<arg2, others...>::value;*/
    };
    

      template<typename... Args>
      struct MaxAlign : std::integral_constant<int, IntegerMax<std::alignment_of<Args>::value...>::value>{};

    /*
    template<typename T, typename... Args>
    struct MaxAlign : std::integral_constant<int,
        (std::alignment_of<T>::value >MaxAlign<Args...>::value ? std::alignment_of<T>::value : MaxAlign<Args...>::value) >
    {};
    
    template<typename T>
    struct MaxAlign<T> : std::integral_constant<int, std::alignment_of<T>::value >{}; */
    
    //是否包含某个类型
    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{};
    
    //获取第一个T的索引位置
    // Forward
    template<typename Type, typename... Types>
    struct GetLeftSize;
    
    // Declaration
    template<typename Type, typename First, typename... Types>
    struct GetLeftSize<Type, First, Types...> : GetLeftSize<Type, Types...>
    {
    };
    
    // Specialized
    template<typename Type, typename... Types>
    struct GetLeftSize<Type, Type, Types...> : std::integral_constant<int, sizeof...(Types)>
    {
        //static const int ID = sizeof...(Types);
    };
    
    template<typename Type>
    struct GetLeftSize<Type> : std::integral_constant<int, -1>
    {
        //static const int ID = -1;
    };
    
    template<typename T, typename... Types>
    struct Index : std::integral_constant<int, sizeof...(Types) - GetLeftSize<T, Types...>::value - 1>{};
    
    //根据索引获取索引位置的类型
    // Forward declaration
    template<int index, typename... Types>
    struct IndexType;
    
    // Declaration
    template<int index, typename First, typename... Types>
    struct IndexType<index, First, Types...> : IndexType<index - 1, Types...>
    {
    };
    
    // Specialized
    template<typename First, typename... Types>
    struct IndexType<0, First, Types...>
    {
        typedef First DataType;
    };
    
    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();
                reinterpret_cast<T*>(data)->~T();
            else
                VariantHelper<Args...>::Destroy(id, data);
        }
    
        inline static void move(type_index old_t, void * old_v, void * new_v)
        {
            if (old_t == type_index(typeid(T)))
                new (new_v) T(std::move(*reinterpret_cast<T*>(old_v)));
            else
                VariantHelper<Args...>::move(old_t, old_v, new_v);
        }
    
        inline static void copy(type_index old_t, const void * old_v, void * new_v)
        {
            if (old_t == type_index(typeid(T)))
                new (new_v) T(*reinterpret_cast<const T*>(old_v));
            else
                VariantHelper<Args...>::copy(old_t, old_v, new_v);
        }
    };
    
    template<> struct VariantHelper<>  {
        inline static void Destroy(type_index id, void * data) {  }
        inline static void move(type_index old_t, void * old_v, void * new_v) { }
        inline static void copy(type_index old_t, const void * old_v, void * new_v) { }
    };
    
    template<typename... Types>
    class Variant
    {
        typedef VariantHelper<Types...> Helper_t;
    
        enum
        {
            data_size = IntegerMax<sizeof(Types)...>::value,
            //align_size = IntegerMax<alignof(Types)...>::value
            align_size = MaxAlign<Types...>::value //ctp才有alignof, 为了兼容用此版本
        };
        using data_t = typename std::aligned_storage<data_size, align_size>::type;
    public:
        template<int index>
        using IndexType = typename IndexType<index, Types...>::DataType;
    
        Variant(void) :m_typeIndex(typeid(void)), m_index(-1)
        {
        }
    
        ~Variant()
        {
            Helper_t::Destroy(m_typeIndex, &m_data);
        }
    
        Variant(Variant<Types...>&& old) : m_typeIndex(old.m_typeIndex)
        {
            Helper_t::move(old.m_typeIndex, &old.m_data, &m_data);
        }
    
        Variant(const Variant<Types...>& old) : m_typeIndex(old.m_typeIndex)
        {
            Helper_t::copy(old.m_typeIndex, &old.m_data, &m_data);
        }

        Variant& operator=(const Variant& old)
        {
          Helper_t::copy(old.m_typeIndex, &old.m_data, &m_data);

          m_typeIndex = old.m_typeIndex;
          return *this;
        }

    
    

        Variant& operator=(Variant&& old)
        {
          Helper_t::move(old.m_typeIndex, &old.m_data, &m_data);

          m_typeIndex = old.m_typeIndex;
          return *this;
        }

        template <class T,
        class = typename std::enable_if<Contains<typename std::remove_reference<T>::type, Types...>::value>::type>
            Variant(T&& value) : m_typeIndex(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 T>
        bool Is() const
        {
            return (m_typeIndex == type_index(typeid(T)));
        }
    
        bool Empty() const
        {
            return m_typeIndex == type_index(typeid(void));
        }
    
        type_index Type() const
        {
            return m_typeIndex;
        }
    
        template<typename T>
        typename std::decay<T>::type& Get()
        {
            using U = typename std::decay<T>::type;
            if (!Is<U>())
            {
                cout << typeid(U).name() << " is not defined. " << "current type is " <<
                    m_typeIndex.name() << endl;
                throw std::bad_cast();
            }
    
            return *(U*) (&m_data);
        }
    
        template<typename T>
        int GetIndexOf()
        {
            return Index<T, Types...>::value;
        }
    
        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)...);
        }
    
        bool operator==(const Variant& rhs) const
        {
            return m_typeIndex == rhs.m_typeIndex;
        }
    
        bool operator<(const Variant& rhs) const
        {
            return m_typeIndex < rhs.m_typeIndex;
        }
    
    private:
        data_t m_data;
        std::type_index m_typeIndex;//类型ID
    };

      测试代码:

    typedef Variant<int, double, string, int> cv;
    //根据index获取类型
    cout << typeid(cv::IndexType<1>).name() << endl;
    
    //根据类型获取索引
    cv v=10;
    int i = v.GetIndexOf<string>();
    
    //通过一组lambda访问vairant
    v.Visit([&](double i){cout << i << endl; }, [&](short i){cout << i << endl; }, [](int i){cout << i << endl; },
        [](string i){cout << i << endl; }
    );
    
    bool emp1 = v.Empty();
    cout << v.Type().name() << endl;

      c++11版本的vairant不仅仅比boost的variant更好用也更强大,经过测试发现性能也优于boost.variant,因此可以在项目中用这个c++11版本的variant替代boost的variant。实际上我的并行计算库中已经用自己的variant和any代替了boost.variant和boost.any,从而消除了对boost的依赖。

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

  • 相关阅读:
    vue 爬坑之路---设置背景图,不能resolve编译
    vue 爬坑之路----flexible.js不适配ipad和ipad pro
    vue-cli3 vue.config.js配置
    vue组件
    Vue 路由按需keep-alive
    vue-cli项目搭建
    http和https
    js时间戳和日期互转换
    vue补充
    js实现头像上传(移动端,PC端均可)
  • 原文地址:https://www.cnblogs.com/qicosmos/p/3559424.html
Copyright © 2011-2022 走看看