【转载】用 C++11 打造好用的 variant (下)

1,040 阅读5分钟

参考原文地址:(原创)用 C++11 打造好用的 variant(原创)用 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::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 派生的访问者类,这个类中定义了访问 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 的依赖