上一篇博文用c++11實現了variant,有童鞋說何不把any也實現一把,我正有此意,它的兄弟variant已經實現了,any也順便打包實現了吧。其實boost.any已經挺好了,就是轉換異常時,看不到詳情,和boost.variant一樣的問題。實現any比實現variant要簡單,需要解決的關鍵技術是類型擦除,關於類型擦除我之前的博文有介紹,想了解的童鞋點這里。
實現any的關鍵技術
any能容納所有類型的數據,因此當賦值給any時,需要將值的類型擦除才行,即以一種通用的方式保存所有類型的數據。這里可以通過繼承去擦除類型,基類是不含模板參數的,派生類中才有模板參數,這個模板參數類型正是賦值的類型,在賦值時,將創建的派生類對象賦值給基類指針,基類的派生類中攜帶了數據類型,基類只是原始數據的一個占位符,通過多態,它擦除了原始數據類型,因此,任何數據類型都可以賦值給他,從而實現了能存放所有類型數據的目標。當取數據時需要向下轉換成派生類型來獲取原始數據,當轉換失敗時打印詳情,並拋出異常。由於any賦值時需要創建一個派生類對象,所以還需要管理該對象的生命周期,這里用unique_ptr智能指針去管理對象的生命周期。
下面來看看一個完整的any是如何實現的吧。
#include <iostream> #include <string> #include <memory> #include <typeindex> struct Any { Any(void) : m_tpIndex(std::type_index(typeid(void))){} Any(const Any& that) : m_ptr(that.Clone()), m_tpIndex(that.m_tpIndex) {} Any(Any && that) : m_ptr(std::move(that.m_ptr)), m_tpIndex(that.m_tpIndex) {} //創建智能指針時,對於一般的類型,通過std::decay來移除引用和cv符,從而獲取原始類型 template<typename U, class = typename std::enable_if<!std::is_same<typename std::decay<U>::type, Any>::value, U>::type> Any(U && value) : m_ptr(new Derived < typename std::decay<U>::type>(forward<U>(value))), m_tpIndex(type_index(typeid(typename std::decay<U>::type))){} bool IsNull() const { return !bool(m_ptr); } template<class U> bool Is() const { return m_tpIndex == type_index(typeid(U)); } //將Any轉換為實際的類型 template<class U> U& AnyCast() { if (!Is<U>()) { cout << "can not cast " << typeid(U).name() << " to " << m_tpIndex.name() << endl; throw bad_cast(); } auto derived = dynamic_cast<Derived<U>*> (m_ptr.get()); return derived->m_value; } Any& operator=(const Any& a) { if (m_ptr == a.m_ptr) return *this; m_ptr = a.Clone(); m_tpIndex = a.m_tpIndex; return *this; } private: struct Base; typedef std::unique_ptr<Base> BasePtr; struct Base { virtual ~Base() {} virtual BasePtr Clone() const = 0; }; template<typename T> struct Derived : Base { template<typename U> Derived(U && value) : m_value(forward<U>(value)) { } BasePtr Clone() const { return BasePtr(new Derived<T>(m_value)); } T m_value; }; BasePtr Clone() const { if (m_ptr != nullptr) return m_ptr->Clone(); return nullptr; } BasePtr m_ptr; std::type_index m_tpIndex; };
測試代碼:
void TestAny() { Any n; auto r = n.IsNull();//true string s1 = "hello"; n = s1; n = "world"; n.AnyCast<int>(); //can not cast int to string Any n1 = 1; n1.Is<int>(); //true }
再總結一下any的設計思路:Any內部維護了一個基類指針,通過基類指針擦除具體類型,any_cast時再通過向下轉型獲取實際數據。當轉型失敗時打印詳情。
c++11 boost技術交流群:296561497,歡迎大家來交流技術。