關於variant的實現參考我前面的博文,不過這第一個版本還不夠完善,主要有這幾個問題:
- 內部的緩沖區是原始的char[],沒有考慮內存對齊;
- 沒有visit功能。
- 沒有考慮賦值構造函數的問題,存在隱患。
這次將解決以上問題,還將進一步增強variant的功能。增加的功能有:
- 通過索引位置獲取類型。
- 通過類型獲取索引位置。
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,歡迎大家來交流技術。
