(原創)用c++11打造好用的any


  上一篇博文用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,歡迎大家來交流技術。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM