首先,any類里面一定要提供一個模板構(gòu)造函數(shù)和模板operator=操作符。其次,數(shù)據(jù)的存放之所是個問題,顯然你不能將它保存在any類中,那會導(dǎo)致any類成為模板類,后者是明確不被允許的。結(jié)論是:為容器準(zhǔn)備一個非泛型的基類,而讓指針指向該基類。
動機(jī)
C++是強(qiáng)類型語言,所有強(qiáng)類型語言對類型的要求都是苛刻的,類型一有不合編譯器就會抱怨說不能將某某類型轉(zhuǎn)換為某某類型,當(dāng)然如果在類型之間提供了轉(zhuǎn)換操作符或是標(biāo)準(zhǔn)所允許的一定程度的隱式轉(zhuǎn)換(如經(jīng)過非explicit構(gòu)造函數(shù)創(chuàng)建臨時變量的隱式轉(zhuǎn)換或是在int,long這些基本類型間的)又另當(dāng)別論。總的說來,為了保持類型安全,C++有嚴(yán)厲的要求。然而有時候程序員可能有這樣的需要:
int i;
iong j;
X x; // 假設(shè)X為用戶定義的類
any anyVal=i;
... //use anyVal as a int value
anyVal=j;
... //use anyVal as a long value
anyVal=x;
... //use anyVal as a long value
考慮這樣的一個“泛型指針類”該如何設(shè)計(jì)是很有趣的事情。
1. 它本身不能是模板類,因?yàn)槿绻悄0澹惚仨殲樗木攥F(xiàn)化提供模板參數(shù)。而事實(shí)上你并不想這樣做。你想讓同一個對象接受任意類型的數(shù)據(jù)。在上面的代碼中這個對象是anyVal。然而,如果你必須為它提供模板參數(shù),那么上面的代碼看起來就會像這樣:
any<int> anyIntVal=i;
any<long> anyLongVal=j;
...
這顯然已經(jīng)喪失了anyVal的優(yōu)勢——以單個對象接受所有類型的數(shù)據(jù)。與其這樣還不如直接寫:
int anyIntVal=i;
int anyLongVal=j;
所以,any不能是模板類。
2. 它必須提供某些有關(guān)它所保存的對象類型的信息。
3. 它必須提供某種方法將它保存的數(shù)值“取出來”。
事實(shí)上,boost庫已經(jīng)提供了這樣的類boost::any,下面我就為你講述它的原理及構(gòu)造。
boost::any原理與結(jié)構(gòu)
首先,any類里面一定要提供一個模板構(gòu)造函數(shù)和模板operator=操作符。因?yàn)槟惚仨氃试S用戶寫出:
any any_value(val); //val 的類型為任意的
any_value=val1; //val1 類型也是任意的
這樣的代碼。
其次,數(shù)據(jù)的存放之所是個問題,顯然你不能將它保存在any類中,那會導(dǎo)致any類成為模板類,后者是明確不被允許的。數(shù)據(jù)應(yīng)該動態(tài)存放,即動態(tài)分配一個數(shù)據(jù)的容器來存放數(shù)據(jù),而any類中則保存指向這個容器的指針,明確地說,是指向這個容器的基類的指針,這是因?yàn)槿萜鞅旧肀仨殲槟0澹?/span>any類中的指針成員又必須不是泛型的(因?yàn)?/span>any不能是泛型的,所以any中所有數(shù)據(jù)成員都不能是泛型的),所以,結(jié)論是:為容器準(zhǔn)備一個非泛型的基類,而讓指針指向該基類。
下面就看一看boost庫是如何具體實(shí)現(xiàn)這兩點(diǎn)的。
摘自”boost/any.hpp”
class any
{
public:
class placeholder // 泛型數(shù)據(jù)容器holder的非泛型基類
{
public:
// 虛析構(gòu)函數(shù),為保證派生類對象能用基類指針析構(gòu)
virtual ~placeholder(){}
public:
// 提供關(guān)于類型的信息
virtual const std::type_info & type() const = 0;
virtual placeholder * clone() const = 0; // 復(fù)制
}; // placeholder
template<typename ValueType>
class holder : public placeholder
{
public:
holder(const ValueType & value)
: held(value)
{}
public:
virtual const std::type_info & type() const
{
// typeid返回std::typeinfo對象引用,后者包含任意對象的類型信息, 如name,此外還提供operator==操作符你可以用typeid(oneObj)==typeid(anotherObj)來比兩個對象之類型是否一致。
return typeid(ValueType);
}
virtual placeholder * clone() const
{
return new holder(held); // 改寫虛函數(shù),返回自身的復(fù)制體
}
public:
ValueType held; // 數(shù)據(jù)保存的地方
}; // holder
// 指向泛型數(shù)據(jù)容器holder的基類placeholder的指針
placeholder * content;
//模板構(gòu)造函數(shù),動態(tài)分配數(shù)據(jù)容器并調(diào)用其構(gòu)造函數(shù)
template<typename ValueType>
any(const ValueType & value)
: content(new holder<ValueType>(value))
{}
...
// 與模板構(gòu)造函數(shù)一樣,但使用了swap慣用手法
template<typename ValueType>
any & operator=(const ValueType & rhs)
{
// 先創(chuàng)建一個臨時對象any(rhs),再調(diào)用下面的swap函數(shù)進(jìn)行底層數(shù)據(jù)交換,注意與*this交換數(shù)據(jù)的是臨時對象,所以rhs的底層數(shù)據(jù)并未被更改,只是在swap結(jié)束后臨時對象擁有了*this的底層數(shù)據(jù),而此時*this也擁有了臨時對象構(gòu)造時所擁有的rhs的數(shù)據(jù)的副本。然后臨時對象由于生命期的結(jié)束而被自動析構(gòu),*this原來的底層數(shù)據(jù)隨之煙消云散。
any(rhs).swap(*this);
return *this;
}
any & swap(any & rhs) //swap函數(shù),交換底層數(shù)據(jù)
{
std::swap(content, rhs.content); // 只是簡單地將兩個指針的值互換
return *this;
}
~any() //析構(gòu)函數(shù)
{
//釋放容器,用的是基類指針,這就是placeholder需要一個虛析構(gòu)函數(shù)的原因
delete content;
}
...
};
這雖然并非any的全部源代碼,但是所有重要的思想已經(jīng)表露無遺。剩下的部分只是一些簡單的細(xì)節(jié),請參見boost庫的原文件。
“但是等等!”,你急切的說:“你失去了類型的信息。”唔...的確,當(dāng)賦值的模板函數(shù)返回后你也就失去了關(guān)于類型的信息。考慮下面你可能想要寫出的代碼:
int i=10;
boost::any anyVal=i;
int j=anyVal;
// error,實(shí)際上你是想把anyVal賦給另一個int型變量,這應(yīng)該以某種方式被允許,但決不是在any類中提供轉(zhuǎn)換操作符,因?yàn)槟闶孪炔⒉恢酪?/span>anyVal來承載何種類型的變量,所以轉(zhuǎn)換操作符無從給出。
當(dāng)轉(zhuǎn)換操作符的設(shè)想徹底失敗后,我們只能借助于某些“外來”的顯式轉(zhuǎn)換操作。就向static_cast<>一樣。boost提供了any_cast<>,于是你可以這樣寫:
int j=any_cast<int>(anyVal);
事實(shí)上,any_cast的代碼是這樣的:
template<typename ValueType>
ValueType any_cast(const any & operand)
{
// 調(diào)用any_cast針對指針的版本。
const ValueType * result = any_cast<ValueType>(&operand);
// 如果cast失敗,即實(shí)際 保存的并非ValueType型數(shù)據(jù),則拋出一個異常。
if(!result)
throw bad_any_cast(); // 派生自std::bad_cast
return *result;
}
而any_cast針對指針的版本是這樣:
template<typename ValueType>
ValueType * any_cast(any * operand)
{
// 這個類型檢查很重要,后面會對它作更詳細(xì)的解釋
return
operand &&
(operand->type()==typeid(ValueType)) ? // #1
&static_cast<any::holder<ValueType>*>(operand->content)->held
: 0; // 這兒有個向下類型轉(zhuǎn)換
}
這兩個any_cast版本應(yīng)該很好理解。此外后一個版本中#1處的類型檢查也是必要的,如果沒有這個檢查,考慮以下代碼:
int i=10;
boost::any anyVal=i;
//如果沒有那個類型檢查,這將通過編譯且運(yùn)行期通常也不會出錯,但是對d的賦值將會是非常奇怪的情形。
double d=any_cast<double>(anyVal);
這將通過編譯,且運(yùn)行期通常竟然也不會出錯,下面我為你解釋為什么會這樣。
boost::anyVal=i;其實(shí)將anyVal.content指針指向了一個holder<int>對象(請回顧上面的代碼)。然后any_cast<double>(anyVal)實(shí)際上調(diào)用了any_cast<>針對指針的重載版本,并將anyVal的地址傳遞過去,也就是轉(zhuǎn)到#1處,因?yàn)檎{(diào)用的是any_cast<double>,所以#1處的代碼被編譯器實(shí)例化為:
// #2
static_cast<any::holder<double> *>(operand->content)->held
但是前面說過,operand->content實(shí)際指向的是any::holder<int>,所以這個static_cast是“非法”的,然而事實(shí)是:它能通過編譯!原因很簡單: holder<double>是placeholder的派生類,而operand->content的類型正是placeholder。從基類指針到派生類指針的轉(zhuǎn)換被認(rèn)為是合法的。但這卻釀成大錯,因?yàn)?strong>表達(dá)式#2的類型將因此被推導(dǎo)為double!原先holder<int>只給int held;成員分配了sizeof(int)個字節(jié)的內(nèi)存,而現(xiàn)在卻要將int型的held當(dāng)作double型來使用,也就是說使用sizeof(double)個字節(jié)內(nèi)存。所以這就相當(dāng)于:
int i=10;
double* pd=(double*)(void*)&i;
// 行為未定義,但通常卻不會出錯,然而隱藏的錯誤更可怕,你得到的d的值幾乎肯定不是你想要的。
double d=*pd;
使用typeinfo讓我們有可能在運(yùn)行時發(fā)現(xiàn)這種類型不符并及時拋出異常。但有個違反直觀的事情是上面的那行錯誤的代碼仍能通過編譯,并且你也無法阻止它通過編譯,因?yàn)?/span>holder<int>和holder<double>都是placeholder的基類。所以只能期望程序員們清楚自己在做什么,要不然就給他個異常瞧瞧。
使用boost::any實(shí)現(xiàn)virtual template成員函數(shù)
如你所知,C++中沒有提供virtual template function。然而有時候你的確會有這種需要,any可以一定程度上滿足這種需要,例如,
class Base
{
public:
virtual void Accept(boost::any anyData)
{
...
}
};
class Derived:public Base
{
public:
virtual void Accept(boost::any anyData)
{
...
}
};
這樣的Accept函數(shù)能夠接受任意類型的數(shù)據(jù),并且是virtual函數(shù)。
目錄(展開《boost源碼剖析》系列文章)
本文來自:http://blog.csdn.net/pongba/archive/2004/08/24/82811.aspx