boost原始碼剖析----boost::any

sld666666發表於2014-09-10

boost原始碼剖析----boost::any

有的時候我們需要有一個萬能型別來進行一些操作,這時候boost::any就派上用場了。

boost::Any testInt(10);
int val = static_cast<int>(testInt);

用法比較簡單,我們來研究下boost::any是如何實現的。

原理

c++是一個強型別的語言,要實現一個萬能型別可以考慮用void*來儲存資料,然後用型別轉換進行操作,如:

class MyAny{
    MyAny(void* input):content_(input){
    }
    
    template<typename T>
    T any_cast(){return *static_cast<T*>(content_)}
    private:
    void* content_;
}

但是這樣的寫法有一個明顯的缺點就是型別不安全。

顯然我們可以用template來改進我們的程式:

template<typename T>
class MyAny{
    MyAny(T input):content_(input){
    }
    
    T any_cast(){return content_;}
    private:
    T content_;
}

但是這樣我們好像就沒有解決問題:vector<MyAny> 好吧,這裡就寫不下去了。

為了能寫下如下的程式碼:

vector<MyAny> items;
items.push_bacck(1);
items.push_bacck(2.0);

我們需要我們的萬能型別有如下的行為:

  1. 對外是一個一般的類,使用者壓入引數的時候不應該關心型別
  2. 它只是一箇中間層,具體儲存資料的應該是一個模板類(Holder)
  3. 必須要能有方法支援任意型別的輸入和輸出為任意型別

實現

我們可以通過新增一箇中間層來解決任何問題。
在boost::any中, 通過兩個中間層來達成我們上面的目標, 類any作為對外的介面層,承擔以模板作為引數並提供必要的對外方法。
類placeholder作為介面類,讓any使用。而holder是一個模板類作為類placeholder的實現者, 這樣就避免的any對泛型引數的要求(能自動推到匯出來)。
我這裡模仿了相關的實現,其程式碼結構應該是這樣的:

class  Any
{
    public:
        Any() :holder_(nullptr){}

        template<typename ValueType>
        Any(const ValueType& val)
            : holder_(new Holder<ValueType>(val)){

        }
    private:
        IHolder* holder_;
    };
    
    mb_interface IHolder{
}

template<typename ValueType>
class Holder : public IHolder{
    public:
        Holder(const ValueType& val) : value_(val){

        }
    }
    
    public:
        ValueType value_;
}

其中Holder提供了具體的資料儲存服務,而 Any提供了對外的介面能力。

其中Holder必須提供兩個方法:

    mb_interface IHolder{
        virtual ~IHolder(){}

        virtual const std::type_info& type() const = 0;

        virtual IHolder* clone() const = 0;
    };
  1. type()提供了查詢型別的能力
  2. clone()提供了產生資料的能力

在 Any中, 提供了以下幾個個介面:

    bool    empty(){
        return !holder_;
    }

    const std::type_info& type() const {
        return holder_ ? holder_->type() : typeid(void);
    }
    
    
    Any& operator=(Any rhs){
        return swap(rhs);
    }

    template<typename ValueType>
    Any& operator=(const ValueType& val){
        return Any(val).swap(*this);
    }

判斷是否為空,查詢型別操作,賦值操作

當然必須還有最重要的any_cast操作,我們看其實現:

template<typename ValueType>
ValueType* anyCast(Any* val){
    return (val && val->type() == typeid(ValueType))
        ? &static_cast<Holder<ValueType>*>(val->getHolder())->value_ : 0;
}

template<typename ValueType>
ValueType anyCast(Any& val){
    ValueType* rtn = anyCast<ValueType>(&val);
    if (!rtn)boost::throw_exception(badAnyCast());
    return *rtn;
}

果然好簡單。呵呵~~~

最後添上單元測試,整個程式碼就完善了:

mb::Any testInt(10);
mb::Any testDouble(10.0);
mb::Any testInt2(testInt);
EXPECT_EQ(testInt.empty(), false);
EXPECT_EQ(std::string(testDouble.type().name()), "double");
EXPECT_EQ(std::string(testInt2.type().name()), "int");

int val = mb::anyCast<int>(testInt);
EXPECT_EQ(val, 10);

總結

  1. 程式碼和boost::any中有一些出入,但是我們的目的是為了研究其實現,就忽略了某些細節
  2. 模板技巧: 模板類原來還可以這麼用---宣告非模板介面,並用模板類實現, 這樣在使用這個介面的時候就能避免宿主類顯示宣告引數型別
  3. boost::any是整個boost庫中最簡單的類之一,但是某些程式碼細節還是非常值得學習和借鑑的。
  4. typeid和type_info 感覺有點像c++中的雞肋,但是某些時候還是有用的
  5. 相關的程式碼上傳到github上,有需要的同學可以看下any.h,
    holder.h

相關文章