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,歡迎大家來交流技術。