boost源码剖析之:Tuple Types(rev#2)
boost源碼剖析之:Tuple Types(rev#2)
?
劉未鵬(pongba)
C++的羅浮宮(http://blog.csdn.net/pongba)
?
Note:?并非新作,04年曾放在blog上,后來(lái)刪掉了,不過(guò)網(wǎng)上到處有轉(zhuǎn)載。這是修改之后的版本。
?
動(dòng)機(jī)[1]
假設(shè)你有這樣一個(gè)函數(shù):它接受兩個(gè)整型數(shù)據(jù)并返回它們整除的結(jié)果,像這樣:
?
int?DevideInts(int?n,int?d)
{
???return?n/d;
}
?
但是我們可能需要更多信息,比如,余數(shù)。函數(shù)的返回值已被占用,我們可以為函數(shù)加一個(gè)參數(shù):
?
int?DevideInts(int?n,int?d,int& Remainder)?
{
????Remainer=n%d;?
????return?n/d;
}
?
但是這樣的函數(shù)形式未免有些拖沓丑陋。我們可以使用std::pair<>來(lái)定義函數(shù)的返回值類型(顧名思義,std::pair<>可以將兩個(gè)值湊成一對(duì)),像這樣:
?
std::pair<int,int> DevideInts(int?n,int?d)
{
????return?std::pair<int,int>(n/d,n%d);
}
?
這是個(gè)可行的方案。簡(jiǎn)潔,優(yōu)雅。
然而,這個(gè)方案只能提供兩個(gè)返回值的捆綁,如果現(xiàn)在需要返回三個(gè)int呢?唔...你可能很快想到這樣組織代碼:
?
std::pair<int,std::pair<int,int> > someFunc();
?
的確,這也能夠工作,但是畢竟不夠精致!如果返回值再增加,代碼將會(huì)愈發(fā)丑陋不堪。另一個(gè)可行的方案是自己定義一個(gè)結(jié)構(gòu)來(lái)保存三個(gè)乃至更多值,然而隨著不同函數(shù)的需要你可能需要定義各種不同的類似這樣的結(jié)構(gòu),這太費(fèi)神了。
所以,我們需要的是一個(gè)高度可復(fù)用的,能夠用來(lái)保存任意型別的任意多個(gè)變量的類——Tuple Types(Tuple的意思是“元組,數(shù)組”)。正如你所想象的,泛型正是提供代碼復(fù)用的最佳手段,它將型別信息抽象出來(lái),直到用戶真正使用那些代碼時(shí),型別信息才得以落實(shí)(所謂“具現(xiàn)化”)。
Boost庫(kù)提供了所謂的Tuple Types,它沒有std::pair的限制,于是你可以寫:
?
//tuple<>目前能夠支持多達(dá)10個(gè)模板參數(shù)
boost::tuple<int,int,int> someFunc();
?
事實(shí)上tuple能夠提供的不止這個(gè),tuple對(duì)IO流的支持能夠允許你寫這樣的代碼:
?
tuple<int,int,int> t(8,9,10);
std::cout<<t;??//輸出(8??9??10)
?
tuple甚至還支持類似的流控制,像這樣:
?
std::cout << tuples::set_open(‘[‘)
<< tuples::set_close(‘]’)
<< tuples::set_delimiter(‘,’)
<< t;
//輸出[8,9,10]
?
好了,你可能已經(jīng)不耐煩了,畢竟,以上的內(nèi)容非常淺顯。然而我必須要告訴你這些,因?yàn)槟闶紫鹊弥纓uple的設(shè)計(jì)目的才能夠去了解它。好在這個(gè)枯燥的過(guò)程已經(jīng)結(jié)束了。深吸一口氣,我們?nèi)タ匆豢磘uple的設(shè)計(jì)細(xì)節(jié)和最本質(zhì)的東西——源代碼。
?
設(shè)計(jì)目標(biāo)
首先,了解tuple的設(shè)計(jì)目標(biāo)十分重要。上面所講的只是一個(gè)總的設(shè)計(jì)目標(biāo)。下面兩個(gè)細(xì)節(jié)設(shè)計(jì)目標(biāo)才是真正需要和體現(xiàn)技術(shù)的地方(并且考慮它們?nèi)绾文軌蜃罴褜?shí)現(xiàn)是非常有趣的事情,當(dāng)然,在你的種種考慮之后,你得承認(rèn),Boost庫(kù)的設(shè)計(jì)無(wú)疑是最精致和高效的),容我向你闡述它們:
?
tuple中的數(shù)據(jù)成員的個(gè)數(shù)應(yīng)該具有某種動(dòng)態(tài)特性。具體的說(shuō)就是如果你像這樣具現(xiàn)化tuple: tuple<int,int> t。則t某種程度上應(yīng)該只需要sizeof(int)*2大小的內(nèi)存來(lái)存放它的數(shù)值,不應(yīng)該有多余的內(nèi)存分配。而如果是tuple<int,int,int> t;則sizeof(t)某種程度上應(yīng)該為sizeof(int)*3。當(dāng)然,你可以利用模板偏特化來(lái)實(shí)現(xiàn)這一點(diǎn)——為提供不同模板參數(shù)個(gè)數(shù)的tuple實(shí)現(xiàn)不同的偏特化版本(也就是說(shuō),對(duì)提供了N個(gè)模板參數(shù)的tuple準(zhǔn)備的偏特化版本中具有N個(gè)數(shù)據(jù)成員)——但是,想想這樣做的代碼數(shù)量吧!你也可以使用動(dòng)態(tài)分配底層容器的策略,然而那會(huì)帶來(lái)額外的負(fù)擔(dān),顯然不如將數(shù)據(jù)直接放在tuple對(duì)象里,況且底層容器又該如何設(shè)計(jì)呢?事實(shí)上,boost::tuple并沒有使用以上任何一種手法,它使用了一種類似Loki庫(kù)[2]里的TypeList設(shè)施的手法來(lái)定義它的底層容器,這種精致的手法利用了某種遞歸的概念,極大的減少了代碼量。后面我會(huì)為你介紹它。
tuple?必須提供某種途徑以獲取它內(nèi)部保存的數(shù)值。類似的,通過(guò)某種編譯期的遞歸,Boost極其巧妙地達(dá)到了這個(gè)目標(biāo)。遺憾的是,由于技術(shù)上的原因,當(dāng)你需要獲取第N個(gè)數(shù)據(jù)時(shí),你所提供的N必須是編譯期可計(jì)算出的常量。這也體現(xiàn)出C++泛型缺少一些運(yùn)行期的特性——是的,C++泛型幾乎完全是編譯期的。
?
其實(shí),雖然上面我只為你描述了兩個(gè)設(shè)計(jì)目標(biāo),但是實(shí)作時(shí)仍會(huì)有各種小問(wèn)題出現(xiàn)。下面的源碼剖析中我會(huì)一一為你解惑。
好吧,在你發(fā)出抱怨聲之前,我還是快點(diǎn)轉(zhuǎn)入我們的主題:
?
boost::tuple源碼剖析
boost::tuple的實(shí)現(xiàn)有許多精妙之處,真是千頭萬(wàn)緒不知從何說(shuō)起。還是從一個(gè)最簡(jiǎn)單的應(yīng)用展開吧:
?
//請(qǐng)記住它,后面我們將一直圍繞這個(gè)例子
boost::tuple<int,long,bool> myTuple(10,10,true);
?
以上簡(jiǎn)單的代碼的背后其實(shí)發(fā)生了很多事,了解了這些事你幾乎就了解了關(guān)于tuple的一大半奧秘。首先我們肯定想知道tuple的聲明是什么樣子的,在boost/tuple/detail/tuple_basic.hpp中聲明了它,其中也包括tuple幾乎所有的實(shí)現(xiàn):
?
template?<?class?T0 = null_type,?class?T1 = null_type,?class?T2 = null_type,
class?T3 = null_type,?class?T4 = null_type,?class?T5 = null_type,
class?T6 = null_type,?class?T7 = null_type,?class?T8 = null_type,
class?T9 = null_type > //?null_type是個(gè)空類
class?tuple;??//?注意這個(gè)聲明的所有模板參數(shù)都有缺省值
?
下面是boost::tuple的定義(也摘自boost/tuple/detail/tuple_basic.hpp):
?
????template?<class?T0,?class?T1,?class?T2,?class?T3,?class?T4,
???????????????class?T5,?class?T6,?class?T7,?class?T8,?class?T9>
????class?tuple :
??????public?detail::map_tuple_to_cons<T0, T1, T2, T3, T4,
T5, T6, T7, T8, T9>::type
{
// tuple的定義體十分簡(jiǎn)單,其中是若干構(gòu)造函數(shù)(將參數(shù)轉(zhuǎn)交給基類)和模板賦值操作符
??…
}; //?為了凸顯重點(diǎn),以下先講tuple的基類
?
其實(shí)tuple本身的定義并無(wú)奧秘和技巧可言,所有秘密都藏在它的基類里面,tuple只是將參數(shù)轉(zhuǎn)交給基類處理。下面我為你剖析它的基類:
?
基類大廈的構(gòu)建
?
構(gòu)建大廈的腳手架——map_tuple_to_cons<>
在我們給出的極其簡(jiǎn)單的應(yīng)用代碼中:tuple<int,long,bool> myTuple(10,10,true);其實(shí)相當(dāng)于:
?
tuple<int,long,bool,
null_type,null_type,null_type,null_type,
null_type,null_type,null_type
>?myTuple(10,10,true);
?
這是因?yàn)閠uple的定義中所有模板參數(shù)都有缺省值,所以你沒有給出值的模板參數(shù)自然會(huì)被編譯器認(rèn)為是缺省值null_type。這樣T0,T1,...,T9分別是int,long,bool,null_type,.....null_type。你發(fā)現(xiàn)基類的表現(xiàn)方式非常怪異——是一個(gè)map_tuple_to_cons<>中的內(nèi)嵌型別::type。很自然,你該知道map_tuple_to_const<>的定義,下面就是:
?
????template?<class?T0,?class?T1,?class?T2,?class?T3,?class?T4,
???????????????class?T5,?class?T6,?class?T7,?class?T8,?class?T9>
????struct?map_tuple_to_cons
????{
????//?cons<>是數(shù)據(jù)的容器,也是所有奧秘所在
????1?typedef?cons<
T0, //第一個(gè)參數(shù)T0被孤立出來(lái)
?????????typename?map_tuple_to_cons< //剩下的模板參數(shù)后跟一個(gè)null_type進(jìn)入下一輪
T1, T2, T3, T4, T5,T6, T7, T8, T9,?null_type
>::type
???????> type;
};
?
以及它的一個(gè)特化版本:
?
template?<>??//這個(gè)特化版本是終止某種遞歸式的自包含定義的關(guān)鍵,后面你會(huì)明白
struct?map_tuple_to_cons<null_type, null_type, null_type, null_type,
null_type, null_type, null_type, null_type,
null_type, null_type>
???{
??????2?typedef?null_type type;
};
?
就這么簡(jiǎn)單。但是它的機(jī)理卻并非那么明顯:上面已經(jīng)知道T0,T1,...,T9被推導(dǎo)為int,long,bool,null_type,...,null_type(其中省略號(hào)表示null_type,下同)。因此tuple的基類:
?
detail::map_tuple_to_cons<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>::type
?
被推導(dǎo)為
?
map_tuple_to_cons<int,long,bool,null_type,...,null_type>::type
?
而根據(jù)map_tuple_to_cons的定義1,這其實(shí)就是:
?
cons<?int,
typename?map_tuple_to_cons<long,bool,null_type,...,null_type>::type
>
?
其中的
typename?map_tuple_to_cons<long,bool,null_type,...,null_type>::type
再一次涉及1處的typedef,因而它被推導(dǎo)為
?
cons<long,typename?map_tuple_to_cons<bool,null_type,...,null_type>::type>
?
所以現(xiàn)在看看基類的定義的形式被推導(dǎo)成為的樣子吧:
?
cons<int,
cons<long,
typename?map_tuple_to_cons<bool,null_type,...,null_type>::type
>?
>?
?
看出端倪了嗎?其中
typename?map_tuple_to_cons<bool,null_type,...,null_type>::type
仍然使用1處的typedef,從而為
cons<bool,
typename?map_tuple_to_cons<null_type,null_type,...,null_type>::type
>?
?
現(xiàn)在,我們推導(dǎo)至這樣一種遞歸嵌套的模式:
?
cons<int,
cons<long,
cons<bool,
typename?map_tuple_to_cons<null_type,...,null_type>::type
>?
>?
>?
?
好了,該是結(jié)束這場(chǎng)游戲的時(shí)候了,你應(yīng)該看出來(lái)了,map_tuple_to_cons<>準(zhǔn)備了一個(gè)特化版本來(lái)作為這場(chǎng)類似繞口令的遞歸式包含的休止符。所以,以上的定義再作最后一重推導(dǎo),使用2處的typedef,將
typename?map_tuple_to_cons<null_type,...,null_type>::type
推導(dǎo)為null_type,得到最終的形式:
?
cons<int,cons<long,cons<bool,null_type> > >?
//?這實(shí)際上只是為int,long,bool各分配一份空間
?
這就是tuple<int,long,bool>的基類!!現(xiàn)在,你應(yīng)該可以類似地推導(dǎo)出:如果tuple的形式為tuple<int,long,bool,double>,則其基類為:
?
cons<int,cons<long,cons<bool,cons<double,null_type> > > >。
?
這樣,隨著你給出的模板參數(shù)個(gè)數(shù)的不同(意味著你要求保存的數(shù)據(jù)的個(gè)數(shù)不同,tuple的基類竟能夠呈現(xiàn)出某種動(dòng)態(tài)的特性(用戶提供的模板參數(shù)個(gè)數(shù)的變化(反映用戶需要保存的數(shù)據(jù)的個(gè)數(shù))導(dǎo)致cons<>容器的嵌套層數(shù)的變化,進(jìn)而導(dǎo)致tuple的底層內(nèi)存的分配量也作相應(yīng)變化)。
map_tuple_to_cons<>以一種遞歸的方式不斷將它的第一個(gè)模板參數(shù)割裂出來(lái),并使tuple的基類呈現(xiàn)像這樣的形式:
?
cons<T0,cons<T1,cons<T2,cons<T3,... ... > > > >
?
這種遞歸當(dāng)map_tuple_to_cons<>的模板參數(shù)都為null_type時(shí)才恰好停止,由于map_tuple_to_cons<>不斷將第一個(gè)模板參數(shù)取出,并將剩余的參數(shù)在尾部添一個(gè)null_type再傳遞下去。所以當(dāng)用戶給出的模板參數(shù)全部被分離出來(lái)時(shí),map_tuple_to_cons<>所接受的參數(shù)就全部都是null_type了,于是使用其特化版本,其中將內(nèi)嵌型別type typedef為null_type。從而結(jié)束這場(chǎng)遞歸。
map_tuple_to_cons<>其實(shí)在tuple的定義中充當(dāng)了十分重要的角色,如果沒有它的介入,難道還有更簡(jiǎn)潔美妙的方式來(lái)達(dá)到這個(gè)目的嗎?
?
構(gòu)建大廈的磚石——cons<>
現(xiàn)在,你一定非常想看一看cons<>的定義,下面就是:
?
template?<class?HT,?class?TT>
???struct?cons {
?????typedef?HT head_type; //?這是個(gè)用戶提供的型別
?????typedef?TT tail_type;???//?這通常是個(gè)cons<>的具現(xiàn)體
?????????????????????????????//?以上兩個(gè)typedef很重要,并非可有可無(wú)
?????typedef
???????typename?detail::wrap_non_storeable_type<head_type>::type
stored_head_type;
3???stored_head_type?head; //?這是其中第一個(gè)數(shù)據(jù)成員
4???tail_type?tail;????????????//?第二個(gè)數(shù)據(jù)成員
?...????????????????????????//?其成員函數(shù)將在后面解釋,此處先略去
};
// cons<>還有一個(gè)偏特化版本:
template?<class?HT>
???struct?cons<HT, null_type> {
typedef?HT head_type;
?????typedef?null_type tail_type;
?????typedef?cons<HT, null_type> self_type;
?????typedef?typename
???????detail::wrap_non_storeable_type<head_type>::type stored_head_type;
?
stored_head_type?head;
//?注意,不像上面的主模板,這里沒有tail成員
?????... //?成員函數(shù)將在后面解釋
};
?
根據(jù)cons<>的定義顯示它有兩個(gè)數(shù)據(jù)成員:3,4兩處描述了它們,對(duì)于第一個(gè)數(shù)據(jù)成員的型別stored_head_type,往它上面看一行,它被typedef為:
?
detail::wrap_non_storeable_type<head_type>::type
//?而head_type又被typedef為HT
?
這又是個(gè)什么玩意?其實(shí)它只是用來(lái)偵測(cè)你是否使用了void型別和函數(shù)類型(所謂函數(shù)型別就是像void(int,int)這樣的型別,它表示接受兩個(gè)int型參數(shù)返回void的函數(shù)的型別,注意,它不同于函數(shù)指針型別,后者形式為void(*)(int,int),void(*f)(int,int)定義了一個(gè)函數(shù)指針f,而void f(int,int)無(wú)疑是聲明了一個(gè)函數(shù)f)來(lái)具現(xiàn)化tuple,如果是的,那它得采取特殊手段,因?yàn)檫@兩種型別不能像int那樣定義它們的變量(你見過(guò)void val;這樣定義val變量的嗎)。“但是”你急忙補(bǔ)充“這本就應(yīng)該不能通過(guò)編譯呀?”是的,寫void?val;這樣的語(yǔ)句不應(yīng)該通過(guò)編譯,寫tuple<void> myTuple;這樣的語(yǔ)句也應(yīng)該不能通過(guò)編譯。但是,typedef?void?VoidType;這樣的typedef卻應(yīng)該是能夠通過(guò)編譯的,所以typedef?tuple<void> voidTupleType;這樣的typedef也該能夠通過(guò)編譯。然而如果在cons<>里單純地寫上:
?
HT head;??//如果HT為void則這將導(dǎo)致編譯錯(cuò)誤
?
這個(gè)成員,則tuple<void>這樣的具現(xiàn)化肯定會(huì)惹惱編譯器(因?yàn)樗鼘?huì)發(fā)覺cons<>里試圖定義一個(gè)void型的變量)。
所以,對(duì)于這種情況,boost使用了wrap_non_storeable_type<>,它的定義是這樣的:
?
template?<class?T>
struct?wrap_non_storeable_type {
typedef?typename?IF<?????????????// IF<>相當(dāng)于編譯期的if...then...else
???????::boost::is_function<T>::value, //?如果為函數(shù)類型則特殊處理
non_storeable_type<T>, T??????//?如果不是函數(shù)類型則type就是T
?????>::RET type;
};
?
以及其特化版本:
?
???template?<>
struct?wrap_non_storeable_type<void> { //?如果為void型也特殊處理
?????typedef?non_storeable_type<void> type;?
???};
?
里面的non_storeable_type<>其實(shí)是函數(shù)型別和void型別的外覆類,以使得它們可以合法的作為數(shù)據(jù)成員被定義。你不能將void?dataMember;作為數(shù)據(jù)成員,但你可以將non_storeable_type<void> wrappedData;作為成員。你不能將void?f(int,int)作為數(shù)據(jù)成員,但你可以將non_storeable_type<void(int,int)> wrapperdData;作為成員。但是,雖然這樣能夠使tuple<void>這樣的型別得以具現(xiàn)出來(lái),然而你仍然不能擁有它們的對(duì)象,像tuple<void> myTuple;這樣的代碼仍然無(wú)法通過(guò)編譯,原因是non_storeable_type<>模板類是這樣定義的:
?
???template?<class?T>
class?non_storeable_type {
??????non_storeable_type();??//?僅有私有的構(gòu)造函數(shù),意味著不能擁有該類的對(duì)象實(shí)體
???};
?
一旦你以tuple<void>為型別定義了一個(gè)變量,則該類內(nèi)部的成員須被初始化,而non_storeable_type<>的構(gòu)造函數(shù)為私有,所以初始化失敗,產(chǎn)生編譯錯(cuò)誤。
所有這些正符合void及函數(shù)型別的特性——能夠被typedef,卻不能擁有數(shù)據(jù)對(duì)象實(shí)體。(boost的實(shí)現(xiàn)者可真夠細(xì)心的)
好了,從細(xì)節(jié)中回過(guò)神來(lái)。我們通常顯然不會(huì)用void和函數(shù)型別來(lái)具現(xiàn)化tuple。所以,通常,cons<>內(nèi)部的兩個(gè)數(shù)據(jù)成員的型別通常其實(shí)就是:
?
?????HT head;
?????TT tail;
?
現(xiàn)在回顧我們的示例代碼:tuple<int,long,bool> myTuple;tuple<int,long,bool>的基類為:
?
????cons<int,cons<long,cons<bool,null_type> > >
?
所以,最外層的cons<>的模板參數(shù)被推導(dǎo)為:
?
typename?HT=int,typename?TT=?cons<long,cons<bool,null_type> >
?
這樣,tuple<int,long,bool>的基類cons<int,cons<long,cons<bool,null_type> > >其實(shí)只擁有兩個(gè)成員:
?
int?head;
cons<long,cons<bool,null_type> > tail; //?注意這又是一個(gè)cons<>對(duì)象
?
tail成員又是cons<>的一個(gè)對(duì)象,不同的是tail的型別不同了——具現(xiàn)化cons<>的模板參數(shù)不同。可想而知,tail內(nèi)部包含兩個(gè)成員:
?
long?head;
cons<bool,null_type> tail;
?
值得注意的是,第二個(gè)tail的型別匹配的是cons<>的偏特化版本,其中只有一個(gè)數(shù)據(jù)成員:
?
bool?head;
?
所以整個(gè)基類的內(nèi)存布局其實(shí)就是cons<>的三重嵌套。三個(gè)head數(shù)據(jù)成員就是需要分配內(nèi)存的主體。如果將這種布局?jǐn)U展,大概就像這樣:
?
?
這種布局正像一種玩具——開始是一個(gè)盒子,揭開盒子其內(nèi)部又是個(gè)更小的盒子,再揭,還是盒子...
現(xiàn)在,基類的內(nèi)存布局已經(jīng)展現(xiàn)在你面前。這一切其實(shí)就是由那個(gè)魔棒般的map_tuple_to_cons<>所造就的,它建造了這種嵌套式的結(jié)構(gòu)。這樣構(gòu)建的好處就是嵌套的重?cái)?shù)可以由用戶給出的模板參數(shù)個(gè)數(shù)來(lái)控制。前者體現(xiàn)了底層內(nèi)存的占用量(如果重?cái)?shù)為N重,則只有N個(gè)head占用內(nèi)存),后者體現(xiàn)用戶的需求量。這正是一種“按需分配”。
在基類的大廈構(gòu)架完畢后,問(wèn)題自然是,如何將材料填入這幢蜂窩般的大廈。這得從tuple的構(gòu)造函數(shù)入手,下面我就帶你作一次跟蹤。
?
初始化的全過(guò)程
然而在跟蹤之前我們須了解tuple的構(gòu)造函數(shù),因?yàn)樗谐跏蓟瘏?shù)由此進(jìn)入:
?
???template?<class?T0,?class?T1,?class?T2,?class?T3,?class?T4,
??????????????class?T5,?class?T6,?class?T7,?class?T8,?class?T9>
???class?tuple :
????public?detail::map_tuple_to_cons<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>::type
???{
???public:
?????typedef?typename
???????detail::map_tuple_to_cons<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>::type
inherited; //?基類
?????typedef?typename?inherited::head_type head_type;
//?基類的head_type(通常即T0,見cons<>的定義)
?????typedef?typename?inherited::tail_type tail_type;
//?基類的tail_type(一般仍為一個(gè)cons<>)
????
?????//下面有十一個(gè)構(gòu)造函數(shù),我只給出兩個(gè),其它類同,只不過(guò)參數(shù)個(gè)數(shù)增加而已
????tuple() {} //?這里也調(diào)用基類的默認(rèn)構(gòu)造函數(shù)
????
// access_traits<>的定義后面解釋
????tuple(typename?access_traits<T0>::parameter_type t0)
?????: inherited(t0, detail::cnull(),????????????// cnull函數(shù)返回null_type()對(duì)象
detail::cnull(), detail::cnull(), //?可將detail::cnull()看作null_type()
detail::cnull(), detail::cnull(),
detail::cnull(), detail::cnull(),
detail::cnull(), detail::cnull())
{ }
????tuple(typename?access_traits<T0>::parameter_type?t0,
????????typename?access_traits<T1>::parameter_type?t1) //增加了一個(gè)參數(shù)t1
????: inherited(t0, t1, detail::cnull(), detail::cnull(),
????????????????detail::cnull(), detail::cnull(), detail::cnull(),
????????????????detail::cnull(), detail::cnull(), detail::cnull())
{ }
????...
??};
?
其中構(gòu)造函數(shù)的參數(shù)型別以access_traits<>來(lái)表現(xiàn)是有原因的,它的定義如下:
?
???template?<class?T>
struct?access_traits {
?????typedef?const?T& const_type;
?????typedef?T& non_const_type;
?????typedef?const?typename?boost::remove_cv<T>::type& parameter_type;
};
?
parameter_type正是在tuple構(gòu)造函數(shù)中被用作參數(shù)型別的。先由remove_cv將T型別可能具有的const或volatile修飾符去掉,然后再加上const修飾符以及表示引用的符號(hào)&,就是parameter_type。舉個(gè)例子,如果我們給T0的模板參數(shù)為int,則typenameaccess_traits<T0>::parameter_type就是const int&。為什么要作這么麻煩的舉動(dòng),就是因?yàn)槟憧赡軙?huì)將常量或臨時(shí)對(duì)象作為參數(shù)傳遞給構(gòu)造函數(shù),而C++標(biāo)準(zhǔn)不允許它們綁定到非const引用。為什么要用引用型別作參數(shù)型別?自然是為了效率著想。
當(dāng)然,如果你想直接在tuple內(nèi)保存引用也可以,如果你將T0賦為int&,這時(shí)候parameter_type并不會(huì)被推導(dǎo)為int&&(引用的引用是非法的),原因是access_traits為此準(zhǔn)備了一個(gè)偏特化版本,如下:
?
???template?<class?T>?struct?access_traits<T&> {
??????typedef?T& const_type;
??????typedef?T& non_const_type;
??????typedef?T& parameter_type;??
???};
?
如果T0本身是個(gè)引用,則對(duì)parameter_type的推導(dǎo)將使用該偏特化版本。不過(guò)你該會(huì)發(fā)現(xiàn)這個(gè)偏特化版本中的parameter_type被定義為T&而非const T&,這是因?yàn)?#xff0c;如果你的意圖是在tuple中保存一個(gè)int&,則出現(xiàn)在構(gòu)造函數(shù)中的參數(shù)的型別就該是int&而非const int&,因?yàn)椴荒苡胏onst int&型別的參數(shù)來(lái)初始化int&型別的成員。
好吧,現(xiàn)在回到我們的例子,我們具現(xiàn)化tuple為tuple<int,long,bool>則該具現(xiàn)體的構(gòu)造函數(shù)應(yīng)該是這樣子:
?
A??tuple(){}
B??tuple(const?int& t0) : inherited(t0, detail::cnull(),...,detail::cnull()){}
C??tuple(const?int& t0,const?long& t1)
: inherited(t0,t1,detail::cnull(),...,detail::cnull())
{ }
D??tuple(const?int& t0,const?long& t1,const?bool& t2)
??: inherited(t0,t1,t2,detail::cnull(),...,detail::cnull())
{ }
E??tuple(const?int& t0,const?long& t1,const?bool& t2,const?null_type&?t3)
?:?inherited(t0,t1,t2,detail::cnull(),..)
{ } //?這不可用
...??//?其他構(gòu)造函數(shù)以此類推
?
這樣一堆構(gòu)造函數(shù),有那些可用呢。事實(shí)上,你可以有以下幾種初始化方法:
?
tuple<int,long,bool> MyTuple; //ok,所有成員默認(rèn)初始化,調(diào)用A
tuple<int,long,bool> MyTuple(10); //ok,第一個(gè)成員賦值為10,其它兩個(gè)默認(rèn)初始化,調(diào)用B
tuple<int,long,bool> MyTuple(10,10);//ok,給第一第二個(gè)成員賦值,調(diào)用C
tuple<int,long,bool> MyTuple(10,10,true);//ok,給三個(gè)成員都賦初始值,調(diào)用D
?
在tuple的構(gòu)造函數(shù)背后發(fā)生了什么事情呢?當(dāng)然是其基類的構(gòu)造函數(shù)被調(diào)用,于是我們跟蹤到cons<>的構(gòu)造函數(shù),它的代碼是這樣的:
?
???template?<class?HT,?class?TT>
struct?cons {
...
?????template?<class?T1,?class?T2,?class?T3,?class?T4,?class?T5,
????????????????class?T6,?class?T7,?class?T8,?class?T9,?class?T10>
?????cons( T1& t1, T2& t2, T3& t3, T4& t4, T5& t5,
???????????T6& t6, T7& t7, T8& t8, T9& t9, T10& t10 )
??????????:?head?(t1),?tail?(t2, t3, t4, t5, t6, t7, t8, t9, t10, detail::cnull())
??{ }
...
};
?
現(xiàn)在假設(shè)我們這樣初始化一個(gè)tuple:
?
tuple<int,long,bool> MyTuple(10,11,true);
?
則調(diào)用tuple的D構(gòu)造函數(shù)被喚起,并將三個(gè)參數(shù)傳給其基類,第一重cons<>將其head賦為10,再將剩下的參數(shù)悉數(shù)傳給其tail,后者又是個(gè)cons<>,它將它的head賦為11(注意,這時(shí)它接受到的第一個(gè)參數(shù)是11),然后將僅剩的true加上后面的九個(gè)null_type一股腦兒傳給它的tail—cons<bool,null_type>(最內(nèi)層的cons<>)。cons<HT,null_type>這個(gè)偏特化版本的構(gòu)造函數(shù)是獨(dú)特的,因?yàn)樗挥衕ead沒有tail成員,所以構(gòu)造函數(shù)的初始化列表里不能初始化tail:
?
???template?<class?HT>
struct?cons<HT, null_type> {
...
????template<class?T1>
????cons(T1& t1,?const?null_type&,?const?null_type&,?const?null_type&,
????????const?null_type&,?const?null_type&,?const?null_type&,
????????const?null_type&,?const?null_type&,?const?null_type&)
????:?head?(t1) {} //?只初始化僅有的head
...
};
?
當(dāng)參數(shù)被傳至最內(nèi)層cons<>,一定是至少有尾部的九個(gè)null_type。這是因?yàn)槿绻阋訬個(gè)模板參數(shù)來(lái)具現(xiàn)化tuple,則你初始化該tuple時(shí)最多只能提供N個(gè)參數(shù),因?yàn)闉镹+i個(gè)參數(shù)準(zhǔn)備的構(gòu)造函數(shù)的第N+1至N+i個(gè)參數(shù)型別將推導(dǎo)為null_type(請(qǐng)回顧上面的各個(gè)構(gòu)造函數(shù),這是因?yàn)槟銢]有提供的模板參數(shù)都默認(rèn)為null_type的緣故),而經(jīng)過(guò)cons<>構(gòu)造函數(shù)的重重“剝削”,直到最內(nèi)層cons<>的構(gòu)造函數(shù)被調(diào)用時(shí),你給出的N個(gè)參數(shù)就只剩一個(gè)了(另外還有九個(gè)null_type)。所以這個(gè)偏特化版本的構(gòu)造函數(shù)與上面的cons<>未特化版本中的并不相同。
這就是初始化的全過(guò)程。然而,事實(shí)上,在上例中,你不一定要將三個(gè)初始化參數(shù)全部給出,你可以給出0個(gè)1個(gè)或者2個(gè)。假設(shè)你這樣寫:
?
tuple<int,long,bool> MyTuple(10);
?
這將調(diào)用tuple的B構(gòu)造函數(shù),后者再將這唯一的參數(shù)后跟九個(gè)null_type傳給其基類—最外層的cons<>,這將使最外層的cons<>將其head初始化為10,然后—它將十個(gè)null_type傳給其tail的構(gòu)造函數(shù),而后者的head為long型數(shù)據(jù)成員,如果后者仍然使用上面給出的構(gòu)造函數(shù),則它會(huì)試圖用它接受的第一個(gè)參數(shù)null_type來(lái)初始化long?head成員,這將導(dǎo)致編譯錯(cuò)誤,然而事實(shí)上這種初始化方式是語(yǔ)意上被允許的,對(duì)于這種特殊情況,cons<>提供了另一個(gè)構(gòu)造函數(shù):
?
???template?<class?T2,?class?T3,?class?T4,?class?T5,
??????????????class?T6,?class?T7,?class?T8,?class?T9,?class?T10>
?????cons(?const?null_type&?t1, //?當(dāng)接受的第一個(gè)參數(shù)為null_type時(shí)
T2& t2, T3& t3, T4& t4, T5& t5,
????????????T6& t6, T7& t7, T8& t8, T9& t9, T10& t10 )
??????:?head?(),?tail?(t2, t3, t4, t5, t6, t7, t8, t9, t10, detail::cnull())
?????{}
?
如果提供的初始化參數(shù)“不夠”,十個(gè)參數(shù)將在cons<>的某一層(還不到最后一層)被“剝削”為全是null_type,這時(shí)將匹配cons<>的這個(gè)構(gòu)造函數(shù),它將head默認(rèn)初始化(head(),而不是head(t1))。而cons<>的偏特化版本亦有類似的版本:
?
???cons(const?null_type&,
???????const?null_type&,?const?null_type&,?const?null_type&,
???????const?null_type&,?const?null_type&,?const?null_type&,
???????const?null_type&,?const?null_type&,?const?null_type&)
??: head () {}
?
這真是個(gè)隱晦繁復(fù)的過(guò)程,但愿你能理清頭緒。既然填充這幢基類“大廈”(cons<>)的材料(初始化tuple的參數(shù))都能夠被安放到位。我們也得清楚如何再將它們?nèi)〕鰜?lái)才是。這個(gè)“取”的過(guò)程又甚為精巧。
?
Tuple的取值過(guò)程
tuple允許你用這樣的方式取值:
?
someTuple.get<N>();??// get是模板函數(shù)
?
其中N必須得是編譯期可計(jì)算的常量。Boost庫(kù)的實(shí)現(xiàn)者不能實(shí)現(xiàn)這樣一個(gè)get版本——它允許你用一個(gè)變量指出想要獲取哪個(gè)元素:
?
someTuple.get(N);?????// N為變量-->錯(cuò)誤
?
這個(gè)事實(shí)是有原因的,原因就在于get函數(shù)的返回值,你知道,用戶可以將不同形式的變量保存在tuple中,但是get函數(shù)是不能在運(yùn)行期決定它的返回值的,返回值必須在編譯期就決議出來(lái)。然而用什么型別作為返回值呢?這取決于你想要保存的哪個(gè)對(duì)象。我們的例子:
?
tuple<int,long,bool> MyTuple;
?
中有三個(gè)變量。如果你寫MyTuple.get<0>()則該get的具現(xiàn)化版本的返回值將被推導(dǎo)為int。如果你寫MyTuple.get<1>()則這個(gè)get的具現(xiàn)化版本返回值將被推導(dǎo)為long。get的模板參數(shù)N就好象下標(biāo),不過(guò)卻是“型別數(shù)組”的下標(biāo)。可見,get的返回值由其模板參數(shù)決定,而所有這些都在編譯期。這就是為什么你不能試圖用變量作“下標(biāo)”來(lái)獲取tuple中的變量的原因。
顯然,我們很關(guān)心這個(gè)get模板函數(shù)是怎樣由它的模板參數(shù)(一個(gè)編譯期整型數(shù))來(lái)推導(dǎo)出其返回值的。事實(shí)上,它通過(guò)一個(gè)traits來(lái)實(shí)現(xiàn)這點(diǎn)。下面是cons<>成員get函數(shù)的源代碼:
?
template?<int?N>
???typename?access_traits<???// access_traits<>上面已經(jīng)講過(guò)
?????typename?element<N, cons<HT, TT> >::type //?element<>就是那個(gè)關(guān)鍵的traits
>::non_const_type //?注意這個(gè)復(fù)雜的返回類型
???get() {
?????return?boost::tuples::get<N>(*this);??//轉(zhuǎn)向全局的get<>函數(shù)
???}
?
所以我們下面跟蹤element<>的推導(dǎo)動(dòng)作。請(qǐng)回顧我們的例子。假設(shè)我們現(xiàn)在寫:
?
MyTuple.get<2>();
?
這將導(dǎo)致tuple<int,long,bool>::get<2>()的返回值被推導(dǎo)為bool。下面就是如何推導(dǎo)的過(guò)程:
首先,最外層cons<>的HT=int,TT=cons<long,cons<bool,null_type> >;而調(diào)用的get正是最外層的。所以,上面的代碼中element<N,cons<HT,TT> >::type被推導(dǎo)為:
?
element<2,cons<int,cons<long,cons<bool,null_type> > > >::type
?
現(xiàn)在來(lái)看一看element<>的定義吧:
?
template<int?N,?class?T> //?這個(gè)int N會(huì)遞減,以呈現(xiàn)遞歸的形式
???struct?element
???{
???private:
?????typedef?typename?T::tail_type?Next;
???????????????????????//?在cons<>內(nèi)部tail_type被typedef為TT,請(qǐng)回顧上面cons<>的代碼
???public:?????????????// cons<>內(nèi)部有兩個(gè)關(guān)鍵的typedef:head_type、tail_type
?????typedef?typename?element<N-1,?Next>::type?type; //遞歸
???};
?
???template<class?T>
???struct?element<0,T>??//遞歸至N=0時(shí),山窮水盡
???{
?????typedef?typename?T::head_type?type; //?山窮水盡時(shí)直接將head_type定義為type
???};
?
它看起來(lái)是如此的精巧簡(jiǎn)練。其中的推導(dǎo)是這樣的:
?
element<>的內(nèi)部有typedef?T::tail_type?Next;所以對(duì)于剛才我們推導(dǎo)出的:
?
element<2,cons<int,cons<long,cons<bool,null_type> > > >::type
?
其中的Next就是cons<int,cons<long,cons<bool,null_type> > >::tail_type,也就是:
?
cons<long,cons<bool,null_type> >
?
element中的type的typedef是這樣的:
?
typedef?typename?element<N-1,?Next>::type type;
?
對(duì)于本例,也就是:
?
typedef?typename?element<1, cons<long,cons<bool,null_type> > >::type?type;
?
同樣的方式,你可以推導(dǎo)出:
?
typename?element<1, cons<long,cons<bool,null_type> > >::type
?
其實(shí)就是:
?
typename?element<0,cons<bool,null_type> >::type
?
這下編譯器得采用element<>的偏特化版本了(因?yàn)榈谝粋€(gè)模板參數(shù)為0),根據(jù)偏特化版本的定義(其中對(duì)type的typedef為:typedef?typename?T::head_type?type;)你可以看出這實(shí)際就是:bool!
唔,經(jīng)過(guò)重重剝削,element<>traits準(zhǔn)確無(wú)誤的將第三個(gè)元素的型別萃取了出來(lái)!
再想一下,如果N為1,那么編譯器將這樣推導(dǎo):
?
?typename?element<1, cons<int,cons<long,cons<bool,null_type> > > >::type
e???????typename?element<0, cons<long,cons<bool,null_type> > >::type
?
第二行編譯器會(huì)決定采用element<>的偏特化版本,從而這就是long!
這是個(gè)由typedef和整型模板參數(shù)的遞減所構(gòu)筑的遞歸世界。編譯期的遞歸!(事實(shí)上,這種編譯期的編程被稱為metaprograming)現(xiàn)在你對(duì)這種遞歸方式應(yīng)該有充分的自信。下面還有——真正取值的過(guò)程又是個(gè)遞歸調(diào)用的過(guò)程。類似的分析方法將再次采用。
請(qǐng)回顧上面給出的get<>的源代碼,其中只有一行——調(diào)用全局的get<>模板函數(shù)并將*this傳遞給它。所以重點(diǎn)是全局的get<>函數(shù),它的源代碼是這樣的:
?
template<int?N,?class?HT,?class?TT>
???inline
typename?access_traits<??// access_traits<>的代碼請(qǐng)回顧上面
?????typename?element<N, cons<HT, TT> >::type
???>::non_const_type????????//?返回類型
???get(cons<HT, TT>& c) {???//?全局的get<>()函數(shù)
??????return?detail::get_class<N>::template?get<
?????????????????typename?access_traits<
?????????????????????typename?element<N, cons<HT, TT> >::type
?????????????????>::non_const_type
>(c);
}
?
你可以輕易看出玄機(jī)都在get_class<N>::template?get<>()上面。下面我將它的代碼挖給你看:
?
template<?int?N >??//這又是個(gè)用作遞歸之用的模板參數(shù)
???struct?get_class {
?????template<class?RET,?class?HT,?class?TT >
?????inline?static?RET get(cons<HT, TT>& t)
?????{
???????return?get_class<N-1>::template?get<RET>(t.tail);
?????}
???};
???template<>
???struct?get_class<0> {
?????template<class?RET,?class?HT,?class?TT>
?????inline?static?RET get(cons<HT, TT>& t)
?????{
???????return?t.head;
?????}
};
?
天哪,這真簡(jiǎn)潔。因?yàn)檫f歸能夠使程序變得簡(jiǎn)潔。這里的遞歸仍然是通過(guò)遞減模板參數(shù)N實(shí)現(xiàn),同時(shí)不斷將t.tail傳給get_class<N-1>::template get<RET>()直到N減為0,從而調(diào)用get_class<0>::get<RET>(),后者直接將t.head返回。就像這樣一種情境:(盒子表示cons<>,通常其中包括head元素和另一個(gè)盒子(cons<>)(除非是偏特化版本的cons<>))
有一排人,第一個(gè)人手里拿著一塊記數(shù)牌和一個(gè)盒子(記數(shù)牌上的數(shù)字表示模板參數(shù)N,盒子當(dāng)然是cons<>數(shù)據(jù)容器)。現(xiàn)在,比如說(shuō),你告訴第一個(gè)人你像要那個(gè)盒子里的4號(hào)(第五個(gè))元素(它深藏在第5重盒子里),他于是將記數(shù)牌上寫上4,然后再減去一,并將盒子打開一層,將里面的小盒子(t.tail,也是個(gè)cons<>容器,cons<>容器不正是一重套一重的嗎?)和記數(shù)牌一并傳給第二個(gè)人,第二個(gè)人將記數(shù)牌上的3減去一,然后再剝?nèi)ヒ粚雍凶?#xff0c;將里面的盒子以及記數(shù)牌(現(xiàn)在是2了)傳給下一個(gè)人,下一個(gè)人做同樣的工作,直到第5個(gè)人(get_class<0>)發(fā)現(xiàn)記數(shù)牌上為0,那么他打開盒子,將里面的head元素傳給第四個(gè),后者再傳給第三個(gè)...,一直傳至你手里。
并且,為了提高效率,get函數(shù)是inline的。
呼~是的,這真夠夸張,并且...不夠優(yōu)雅!?是的,或許它的代碼非常丑陋,然而隱藏在它背后的思想確實(shí)無(wú)與倫比的優(yōu)雅和精巧。更何況對(duì)于一個(gè)能夠應(yīng)付千萬(wàn)種情況,并具備高度復(fù)用性的類,這樣的實(shí)在可算是夠“優(yōu)雅”的了。
另外boost還提供了一個(gè)length<>來(lái)獲得tuple的長(zhǎng)度(即所含元素個(gè)數(shù))
?
template<class?T>
???struct?length {
?????static const?int?value = 1 +?length<typename?T::tail_type>::value; //遞歸
};
???template<>
???struct?length<null_type> {
????static const?int?value = 0;
};
?
我想,有了上面的經(jīng)驗(yàn),這種編譯期遞歸對(duì)于你應(yīng)該了無(wú)秘密。我就不多說(shuō)了。length<>位于namespace tuples里面。
?
最后一點(diǎn)細(xì)節(jié)
??為了方便用戶,boost庫(kù)還提供了make_tuple和tie函數(shù),前者很簡(jiǎn)單:產(chǎn)生一個(gè)臨時(shí)的tuple,你可以這樣使用它:
?
???tuple<int,long,bool> MyTuple=make_tuple(10,10,true);
?
??而tie則意為將參數(shù)綁在個(gè)tuple里面,不同的是因?yàn)槭墙?#xff0c;所以它返回的tuple保存引用,像這樣使用它:
?
???int ival=10;??long lval=10; bool bval=true;
???tuple<int&,long&,bool&> MyTuple=tie(ival,lval,bval);
???... //?這里,你修改MyTuple里的數(shù)據(jù)會(huì)直接影響到ival,lval,bval;
?
??你還可以用一行代碼來(lái)更改三個(gè)變量的值,像這樣:
?
???tie(ival,lval,bval)=make_tuple(9,9,false); //?同時(shí)更改了三個(gè)變量值
?????????????????????????????????????????????//?現(xiàn)在ival,lval,bval分別為9,9,false。
?
??你還可以忽略make_tuple()返回的部分值,像這樣:
???tie(ival,tuples::ignore,bval)=make_tuple(9,9,false);
//?只有ival,bval被更改,lval維持原值
??????// tuples::ignore是個(gè)預(yù)定義的對(duì)象,它有一個(gè)模板化的operator =函數(shù),
//?從而可以接受向它賦的任何值。
?
本文沒有涉及的
本文沒有涉及tuple對(duì)IO的支持——實(shí)際上它幾乎只是對(duì)tuple中的每一個(gè)元素進(jìn)行輸出。
?
本文沒有涉及tuple的拷貝構(gòu)造函數(shù),cons<>的拷貝構(gòu)造函數(shù),以及cons<>的const成員函數(shù)——事實(shí)上,在了解了以上那些秘密后,這就微不足道了。
?
本文沒有涉及tuple提供的比較函數(shù)——事實(shí)上那比較簡(jiǎn)單,它只是轉(zhuǎn)而比較各個(gè)元素。
?
目錄(展開《boost源碼剖析》系列文章)
總結(jié)
以上是生活随笔為你收集整理的boost源码剖析之:Tuple Types(rev#2)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: boost源码剖析之:boost::mu
- 下一篇: boost源码剖析之:泛型函数指针类bo