boost源码剖析之:boost::multi_array
boost源碼剖析之:boost::multi_array
?
謝軒?劉未鵬
C++的羅浮宮(http://blog.csdn.net/pongba)
?
Note:?并非新作,是以前和老朋友謝軒寫(xiě)的,也可以在謝軒的blog上找到。?
?
動(dòng)機(jī)
??????C++是一門(mén)自由的語(yǔ)言,允許你自由的表達(dá)自己的意圖,對(duì)不對(duì)??所以我們既然可以new一個(gè)一維數(shù)組,也應(yīng)該可以new出多維數(shù)組,對(duì)不對(duì)?先來(lái)看一個(gè)例子:
?????????int* pOneDimArr = new int[10]; //新建一個(gè)10個(gè)元素的一維數(shù)組
?????????pOneDimArr[0] = 0; //訪問(wèn)
?????????int**?pTwoDimArr = new int[10][20]; //錯(cuò)誤!
?????????pTwoDimArr[0][0]?= 0; //訪問(wèn)
?????但是,很可惜,三四兩行代碼的行為并非如你所想象的那樣——雖然從語(yǔ)法上它們看起來(lái)是那么“自然”。
?????這里的問(wèn)題在于,new int[10][20]返回的并非int**類(lèi)型的指針,而是int (*)[20]類(lèi)型的指針(這種指針被稱(chēng)為行指針,對(duì)它“+1”相當(dāng)于在數(shù)值上加上一行的大小(本例為20),也就是說(shuō),讓它指向下一行),所以我們的代碼應(yīng)該像這樣:
int (*pTwoDimArr)[20] = new int[i][20]; //正確
pTwoDimArr[1][2] = 0; //訪問(wèn)
?????注意pTwoDimArr的類(lèi)型——int(*)[20]是個(gè)很特殊的類(lèi)型,它不能轉(zhuǎn)化為int**,雖然兩者索引元素的語(yǔ)法形式一樣,都是“p[i][j]”的形式,但是訪問(wèn)內(nèi)存的次數(shù)卻不一樣,語(yǔ)義也不一樣。
?????最關(guān)鍵的問(wèn)題還是:以上面這種樸素的方式來(lái)創(chuàng)建多維數(shù)組,有一個(gè)最大的限制,就是:除了第一維,其它維的大小都必須是編譯期確定的。例如:
?????int (*pNdimArr)[N2][N3][N4] = new int[n1][N2][N3][N4];
?????這里N2,N3,N4必須都是編譯期常量,只有n1可以是變量,這個(gè)限制與多維數(shù)組的索引方式有關(guān)——無(wú)論多少維的數(shù)組都是線性存儲(chǔ)在內(nèi)存中的,所以:
?????????pTwoDimArr[i][j] = 0;
被編譯器生成的代碼類(lèi)似于:
?????????*( (int*)pTwoDimArr+i*20+j ) = 0;
?????20就是二維數(shù)組的行寬,問(wèn)題在于,如果允許二維數(shù)組的行寬也是動(dòng)態(tài)的,這里編譯器就無(wú)法生成代碼(20所在的地方應(yīng)該放什么呢?)。基于這個(gè)原因,C++只允許多維數(shù)組的第一維是動(dòng)態(tài)的。
?????不幸的是,正由于這個(gè)限制,C++中的多維數(shù)組就在大多數(shù)情況下變成了有名無(wú)實(shí)的無(wú)用之物。我們經(jīng)常可以在論壇上看到關(guān)于多維數(shù)組的問(wèn)題,一般這類(lèi)問(wèn)題的核心都在于:如何模仿一個(gè)“完全動(dòng)態(tài)的”多維數(shù)組。這里“完全動(dòng)態(tài)”的意思是,所有維的大小都可以是動(dòng)態(tài)的變量,而不僅是第一維。論壇上給出的答案不一而足,有的已經(jīng)相當(dāng)不錯(cuò),但是要么缺乏可擴(kuò)展性(即擴(kuò)展到N維的情況),要么在訪問(wèn)元素的形式上遠(yuǎn)遠(yuǎn)脫離了內(nèi)建的多維數(shù)組的訪問(wèn)形式,要么消耗了額外的空間。歸根到底,我們需要的是一個(gè)類(lèi)似這樣的多維數(shù)組實(shí)現(xiàn):
?
?????//創(chuàng)建一個(gè)int型的3維數(shù)組,dim_sizes表示各維的大小:n1*n2*n3
?????multi_array<int,3> ma (?dim_sizes[n1][n2][n3] );
?????ma[i][j][k] = value; //為第i頁(yè)j行k列的元素賦值
?????ma[i][j] = value; //編譯錯(cuò)!
?????ma[i] = value; //編譯錯(cuò)!
?????ma[i][j][k][l] = value;//編譯錯(cuò)!
?
這樣一個(gè)multi_array,能夠自動(dòng)管理內(nèi)存,擁有和內(nèi)建多維數(shù)組一致的界面,并且各維的大小都可以是變量——正符合我們的要求。看起來(lái),實(shí)現(xiàn)這個(gè)multi_array并非難事,但事實(shí)總是出乎想象,下面就是對(duì)boost中已有的一個(gè)multi_array實(shí)現(xiàn)的剖析——你幾乎肯定會(huì)發(fā)現(xiàn)一些出乎意料的(甚至是令人驚奇的)地方。
?
Boost中的多維數(shù)組實(shí)現(xiàn)——boost::multi_array
?
在Boost庫(kù)中就有一個(gè)用于描述多維數(shù)組的功能強(qiáng)大的MultiArray庫(kù)。它實(shí)現(xiàn)了一個(gè)通用、與標(biāo)準(zhǔn)庫(kù)的容器一致的接口,并且具有與C++中內(nèi)建的多維數(shù)組一樣的界面和行為。正是這種設(shè)計(jì),使得MultiArray庫(kù)與標(biāo)準(zhǔn)庫(kù)組件甚至用戶自定義的泛型組件之間可以具有很好的兼容性,使它們能夠很好協(xié)同工作。除此之外,MultiArray還提供了諸如改變大小、重塑(reshaping)以及對(duì)多維數(shù)組的視圖訪問(wèn)等極為有用的特性,從而使MultiArray比其它描述多維數(shù)組的組件(譬如:std::vector< std::vector<…> >?)更為便捷、高效。對(duì)示例程序進(jìn)行調(diào)試、跟蹤是分析庫(kù)源代碼最有效的手段之一。我們就從MultiArray文檔中的示例程序入手:
?
//?略去頭文件包含
int?main () {
?????//?創(chuàng)建一個(gè)尺寸為3×4×2的三維數(shù)組
?????#define?DIMS?3?//數(shù)組是幾維的
?????typedef?boost::multi_array<double,DIMS> array_type; // (1-1)
?????array_type A(boost::extents[3][4][2]);???//?(1-2)
?????//?為數(shù)組中元素賦值
?????A[1][2][0]?= 120;??????//?(1-3)
?????... ...
?????return?0;
}
?
在上述代碼中,(1-1)處的typedef是我們程序中使用的三維數(shù)組類(lèi)型的聲明,很明顯,boost::multi_array的兩個(gè)模板參數(shù)分別代表數(shù)組元素的類(lèi)型和數(shù)組的維度。而(1-2)處就是三維數(shù)組對(duì)象的構(gòu)造語(yǔ)句。boost::extents[3][4][2]的意思是:定義一個(gè)3*4*2的三維數(shù)組。
下面我就為你層層剝開(kāi)boost::extents的所有奧秘——
?
extents——與內(nèi)建數(shù)組一致的方式
boost::extents是一個(gè)全局對(duì)象,在base.hpp中:
?
?????typedef?detail::multi_array::extent_gen<0>?extent_gen;
?????... ...
?????multi_array_types::extent_gen?extents; //注意它的類(lèi)型!
?
可見(jiàn)extents的類(lèi)型為extent_gen,這個(gè)extend_gen則位于extent_gen.hpp中:
?
// extent_gen.hpp
?????template?<std::size_t NumRanges>
?????class?extent_gen?{
???????range_list ranges_;????//?(2-1)
???????... ...
???????extent_gen(const?extent_gen<NumRanges-1>& rhs,?const?range& a_range)??????????????????//?(2-2)
???????{
std::copy(rhs.ranges_.begin(),rhs.ranges_.end(),ranges_.begin());
??????????????*ranges_.rbegin() = a_range;
???????}
???????extent_gen<NumRanges+1>
??????????????operator[](index idx)??//?(2-3)
?????????{?return?extent_gen<NumRanges+1>(*this,range(0,idx)); }
???????};
?
?
所以,boost::extents[3][4][2]展開(kāi)為操作符調(diào)用的方式就相當(dāng)于:
extents.operator?[](3).operator?[](4).operator?[](2);
extents是extent_gen<0>類(lèi)型的,extents.operator [](3)應(yīng)調(diào)用函數(shù)(2-3),此時(shí)NumRange為0,而返回類(lèi)型是extent_gen<1>;再以該返回對(duì)象調(diào)用operator [](4),此時(shí)NumRange為1,而返回類(lèi)型則是extent_gen<2>了。再看函數(shù)(2-3)的內(nèi)容,其實(shí)就是將參數(shù)idx以range包裝一下再轉(zhuǎn)發(fā)給構(gòu)造函數(shù)(2-2),注意此時(shí)調(diào)用的是extent_gen<NumRange+1>類(lèi)型的構(gòu)造函數(shù)。至于range(0,idx)則表示一個(gè)[0,idx)的區(qū)間。進(jìn)入構(gòu)造函數(shù)(2-2),我們注意到extent_gen<...>中具有public的成員ranges_,聲明位于(2-1)處,而ranges_就是一個(gè)容器,保存了一系列的range。
跟蹤這些代碼,基本了解了extents的工作方式:每調(diào)用一次operator [],都會(huì)返回一個(gè)extent_gen<NumRange+1>類(lèi)型的對(duì)象,所以,對(duì)于boost::extents[3][4][2],依次返回的是:
extent_gen<1> => extent_gen<2> => extent_gen<3>
最后一個(gè)也是最終的返回類(lèi)型——extent_gen<3>。其成員ranges_中,共有[0,3)、[0,4)、[0,2)三組區(qū)間。這三組區(qū)間指定了我們定義的multi_array對(duì)象的三個(gè)維度的下標(biāo)區(qū)間,值得注意的是這些區(qū)間都是前閉后開(kāi)的,即不包含上界值,這一點(diǎn)在后面的代碼中能夠看到。當(dāng)boost::extents準(zhǔn)備完畢后,就被傳入multi_array的構(gòu)造函數(shù),用于指定各維的下標(biāo)區(qū)間:
?
?????// multi_array.hpp
?????explicit?multi_array(const?extent_gen<NumDims>& ranges) :
?????super_type((T*)initial_base_,ranges) {
?????????allocate_space();??//?(2-5)
?????}
?
這里,multi_array接受了ranges參數(shù)中的信息,取出其中各維的下標(biāo)區(qū)間,然后保存,最后調(diào)用allocate_space()來(lái)分配底層內(nèi)存。
?
使用extent_gen的好處
使用boost::extents作參數(shù)的構(gòu)造過(guò)程和內(nèi)建多維數(shù)組的方式一致,簡(jiǎn)練直觀,語(yǔ)義清晰。首先,boost::extents使用“[]”,能讓人很容易想到內(nèi)建多維數(shù)組的聲明,也很清晰地表達(dá)了每個(gè)方括號(hào)中數(shù)值的含義——表明各維度的容量區(qū)間;最關(guān)鍵的還是,使用boost::extents,可以防止用戶寫(xiě)出錯(cuò)誤的代碼,例如:
multi_array<int,3> A(boost::extents[3][4][2][5]);//錯(cuò)!多了一維!
上面的語(yǔ)句是無(wú)法通過(guò)編譯,因?yàn)閙ult_array是個(gè)三維數(shù)組,而boost::extents后面卻跟了四個(gè)“[]”,這顯然是個(gè)錯(cuò)誤;在語(yǔ)法層面,由于multi_array<int,3>的構(gòu)造函數(shù)只能接受extent_gen<3>類(lèi)型的參數(shù),而根據(jù)我們前面對(duì)extents的分析,boost::extents[3][4][2][5]返回的卻是extent_gen<4>類(lèi)型的對(duì)象,于是就會(huì)產(chǎn)生編譯錯(cuò)誤。這種編譯期的強(qiáng)制措施阻止了用戶一不小心犯下的錯(cuò)誤(如果你正在打瞌睡呢?),也很清晰明了地表達(dá)(強(qiáng)制)了語(yǔ)義的需求。
?
另一種替代方案及其缺點(diǎn)
另外,還有一種聲明各維大小的替代方式,就是使用所謂的Collection Concept,例如:
?
//?聲明一個(gè)shape(“形狀”),即各個(gè)維度的size
?????boost::array<int,3>?shape?= {{?3,?4,?2?}};
?????array_type B(shape);?//3*4*2的三維數(shù)組
?
這種方式將調(diào)用multi_array的第二種構(gòu)造函數(shù):
?
?????// multi_array.hpp
?????template?<class?ExtentList>
?????explicit?multi_array(?ExtentList?const&?extents?) :
?????super_type((T*)initial_base_,extents) {
?????????boost::function_requires< //?(2-4)
?????detail::multi_array::CollectionConcept<ExtentList> >();
?????????allocate_space();??//?(2-6)
?????}
?
????這個(gè)構(gòu)造函數(shù)的形參extents只要是符合collection concept就可以了——shape的類(lèi)型為boost::array,當(dāng)然符合這個(gè)concept。這個(gè)構(gòu)造函數(shù)的行為與接受extents_gen的構(gòu)造函數(shù)是一樣的——仍然是先取出各維的range保存下來(lái),然后分配底層內(nèi)存。至于(2-4)處的代碼,功能就是在編譯期檢查模板參數(shù)ExtentList是否符合Collection concept,實(shí)現(xiàn)細(xì)節(jié)在此不再贅述。
把這種方式與使用extent_gen的方式作一個(gè)簡(jiǎn)單的比較,很容易就看出優(yōu)劣:采用這種方式,就不能保證編譯期能夠進(jìn)行正確性的檢查了,例如:
?
boost::array<int,4>?shape?= {{3,4,2,5}}; //一個(gè)四維數(shù)組的shape
multi_array<int,3> A(shape); //?竟然可以通過(guò)編譯!!
?
這里,用一個(gè)四維的shape來(lái)指定一個(gè)三維multi_array顯然是錯(cuò)誤的,但是居然通過(guò)了編譯,這是由于這個(gè)構(gòu)造函數(shù)將它的參數(shù)extents作為一個(gè)普通的collection來(lái)對(duì)待,構(gòu)造函數(shù)根據(jù)自己的需求用iterator從extents中取出它所需要的數(shù)值——A是三維數(shù)組,于是構(gòu)造函數(shù)從shape中取出前三個(gè)數(shù)值作為A三個(gè)維度的下標(biāo)區(qū)間,而不管shape究竟是包含了幾個(gè)數(shù)值。這樣的語(yǔ)句在語(yǔ)義上是不清晰甚至錯(cuò)誤的。但是既然這樣的構(gòu)造函數(shù)存在,設(shè)計(jì)者自然有他的道理,文檔中就明確的表明,這個(gè)構(gòu)造函數(shù)最大的用處就是編寫(xiě)維度無(wú)關(guān)(dimension-independent)的代碼,除此之外multi_array庫(kù)默認(rèn)為前一種構(gòu)造函數(shù)。
?
multi_array的架構(gòu)
無(wú)論采用哪一種構(gòu)造函數(shù),代碼流程卻是相似的——將一系列下標(biāo)區(qū)間傳入基類(lèi)的構(gòu)造函數(shù)中去,基類(lèi)構(gòu)造完成之后就調(diào)用相同的allocate_space()函數(shù)(見(jiàn)(2-5)和(2-6)處),allocate_space,顧名思義,應(yīng)該是為多維數(shù)組的元素分配空間的。但是對(duì)于這樣在派生類(lèi)而非基類(lèi)的構(gòu)造中分配存儲(chǔ)空間的設(shè)計(jì),可能的合理解釋就是:基類(lèi)是個(gè)適配器(adapter),它決定了一切對(duì)原始數(shù)據(jù)的訪問(wèn)規(guī)則,描述了multi_array對(duì)外界的接口。
順著基類(lèi)的構(gòu)造函數(shù),我們繼續(xù)向multi_array的深處探索。
????
// multi_array_ref.hpp
template?<typename?T, std::size_t NumDims>
class?multi_array_ref?:??//multi_array的基類(lèi)!!
public?const_multi_array_ref<T,NumDims,T*>
{
?????typedef?const_multi_array_ref<T,NumDims,T*>?super_type;
?????... ...
?????explicit?multi_array_ref(T*?base, //指向數(shù)組存儲(chǔ)空間的指針
?????????const?extent_gen<NumDims>& ranges): //下標(biāo)區(qū)間
?????super_type(base,ranges)???//把初始化的任務(wù)轉(zhuǎn)發(fā)給基類(lèi)(3-1)
?????{ }
?????????... ...
};
?
// multi_array_ref.hpp
class?const_multi_array_ref?:?//multi_array_ref的基類(lèi)!管理底層存儲(chǔ)!
public?multi_array_impl_base<T,NumDims>
{
?????... ...
?????explicit?const_multi_array_ref(TPtr base,
?????????const?extent_gen<NumDims>& ranges) :
?????????base_(base),?storage_(c_storage_order())???//?(3-2)
?????????{ init_from_extent_gen(ranges); }
?????... ...
?????storage_order_type?storage_;//支持多種存儲(chǔ)策略!(3-3)
????
};
?
multi_array基類(lèi)對(duì)象的構(gòu)造之路途徑(3-1)處multi_array_ref的構(gòu)造函數(shù),延伸至(3-2)處const_multi_array_ref的構(gòu)造函數(shù)——這里看似一個(gè)終結(jié),因?yàn)樵贈(zèng)]有參數(shù)傳遞給const_multi_array_ref的基類(lèi)multi_array_impl_base了。但是心中還是疑惑:為什么會(huì)有如此多層的繼承結(jié)構(gòu)?這樣的類(lèi)層次結(jié)構(gòu)設(shè)計(jì)究竟有什么玄機(jī)呢?
?
多層繼承的奧秘——復(fù)用性
轉(zhuǎn)到基類(lèi)const_multi_array_ref的聲明,似乎可以看出一些端倪:
?
template< ... >
class?const_multi_array_ref?{
?????... ...
?????//和所有的STL容器一致的迭代器界面!!
?????const_iterator?????begin()?const;
?????const_iterator?????end()?const;
?????... ...
?????//和std::vector一致的元素訪問(wèn)界面!!
?????const_reference?operator[](index i)?const;
?????... ...
};
?
看到上面這些聲明,是不是有些面熟?STL!對(duì),這些成員函數(shù)的聲明是與STL中container concept完全一致的。所謂與STL的兼容性正是在這里體現(xiàn)出來(lái)了。而const_multi_array_ref更是“類(lèi)如其名”,const_multi_array_ref中所有訪問(wèn)元素、查詢數(shù)組信息等成員函數(shù)都返回const的reference或iterator。而反觀multi_array_ref的聲明,其中只比const_multi_array_ref多了訪問(wèn)元素、查詢數(shù)組信息的對(duì)應(yīng)的non-const版本成員函數(shù)。那么const_multi_array_ref的基類(lèi)multi_array_impl_base的職責(zé)是什么呢?接著展開(kāi)類(lèi)multi_array_impl_base的聲明,
multi_array_impl_base是屬于實(shí)現(xiàn)細(xì)節(jié)的,它的作用只是根據(jù)數(shù)組信息(const_multi_array_ref中的成員變量)計(jì)算偏移量、步長(zhǎng)等,也就是把多維的下標(biāo)最終轉(zhuǎn)化為一維偏移量。而multi_array_impl_base的基類(lèi)——或者是value_accessor_n或者是value_accessor_one——的功能就是提供一個(gè)對(duì)原始數(shù)據(jù)的訪問(wèn)。這在下文詳述。
至此,對(duì)multi_array的基類(lèi)子對(duì)象大致有了了解——它們的繼承關(guān)系如下:
????multi_array -> multi_array_ref -> const_multi_array_ref -> multi_array_impl_base -> value_accessor_n/value_accessor_one
其中每一層都擔(dān)任各自的角色:
¨????????multi_array :?為數(shù)組元素分配空間,將各種操作轉(zhuǎn)發(fā)至基類(lèi)。
¨????????multi_array_ref :?提供與STL容器一致的數(shù)據(jù)訪問(wèn)界面。也可以獨(dú)立出來(lái)作為一個(gè)adapter使用。
¨????????const_multi_array_ref :?提供const的STL數(shù)據(jù)訪問(wèn)界面。也可以作為一個(gè)const adapter使用。
¨????????multi_array_impl_base及其基類(lèi)?:最底層實(shí)現(xiàn),提供一組對(duì)原始數(shù)據(jù)的基本操作。
這種架構(gòu)看似復(fù)雜,卻提供了極高的復(fù)用性,其中的(const_)multi_array_ref都可以獨(dú)立出來(lái)作為一個(gè)adapter使用——例如:
?
????int a[24]; //一維的10個(gè)元素?cái)?shù)組
????//把一維數(shù)組a看成一個(gè)3*4*2的三維數(shù)組:
????multi_array_ref<int,3> arr_ref(a,boost::extents[3][4][2]);
????arr_ref[i][j][k] = value; //和multi_array一樣的使用界面
?
倘若你不想讓multi_array來(lái)自動(dòng)分配內(nèi)存的話,你可以自行分配數(shù)組(可以位于棧上或堆上)然后用multi_array_ref把它包裝成一個(gè)多維的數(shù)組。
?
multi_array的存儲(chǔ)策略
接下來(lái),就來(lái)看看multi_array的存儲(chǔ)策略,例如:C風(fēng)格的多維數(shù)組存儲(chǔ)方式是按行存儲(chǔ),而fortran恰恰相反,是按列存儲(chǔ),甚至,用戶可能有自己的存儲(chǔ)策略要求。那么,如何支持多種風(fēng)格的存儲(chǔ)策略呢?秘密就在于代碼(3-3)處,const_multi_array_ref的成員storage_——其類(lèi)型為storage_order_type,下面的聲明指出了storage_order_type的“本來(lái)面目”——general_storage_order<NumDims>:
????
// multi_array_ref.hpp
?????... ...
?????typedef?general_storage_order<NumDims> storage_order_type;
?????... ...
?????// storage_order.hpp
?????template?<std::size_t NumDims>
?????class?general_storage_order?{
?????general_storage_order(const?c_storage_order&){ //(4-1)
?????????for?(size_type i=0; i != NumDims; ++i)
?????????{ ordering_[i] = NumDims -?1?- i; }
?????????ascending_.assign(true);
?????????}
?????... ...
?????boost::array<size_type,NumDims>?ordering_;
?????boost::array<bool,NumDims>?ascending_;
?????};
?
在(4-1)處的構(gòu)造函數(shù)中,ordering_和ascending_是兩個(gè)數(shù)組,當(dāng)函數(shù)(4-1)執(zhí)行完畢后,ordering_中的元素應(yīng)當(dāng)是{NumDims-1, NumDims-2,...1,0},如果將這些元素作為各維度存儲(chǔ)順序的標(biāo)識(shí)——具有較小ordering_值的維度先存儲(chǔ)——那么這和C語(yǔ)言中的存儲(chǔ)方式就完全一致了,ascending_勿庸置疑就是用來(lái)表明各維度是否升序存儲(chǔ)。其實(shí)general_storage_order還有一個(gè)模板構(gòu)造函數(shù),它是為了支持更為一般化的存儲(chǔ)策略(例如fortran的按列存儲(chǔ)或用戶自定義的存儲(chǔ)策略)。這里不作詳述。
除了存儲(chǔ)策略,const_multi_array_ref的構(gòu)造還通過(guò)調(diào)用init_from_extent_gen函數(shù),將extents中的內(nèi)容取出來(lái)進(jìn)行處理,并以此設(shè)定其它若干表述多維數(shù)組的變量((3-3)處其它一些變量),具體細(xì)節(jié)不再贅述。
現(xiàn)在關(guān)于一個(gè)多維數(shù)組的所有信息都已經(jīng)準(zhǔn)備齊備,可謂“萬(wàn)事具備,只欠‘空間’”。multi_array下面要做的就是調(diào)用前面提到的allocate_space來(lái)為數(shù)組中的元素分配空間了。
?
// multi_array.hpp
void?allocate_space() {
?????... ...
?????base_ = allocator_.allocate(this->num_elements(),no_hint);
?????... ...??std::uninitialized_fill_n(base_,allocated_elements_,T());
}
?
原來(lái)在底層,存儲(chǔ)仍然是退化為一維數(shù)組的存儲(chǔ),std::uninitialized_fill_n負(fù)責(zé)把該數(shù)組進(jìn)行缺省初始化。allocate_space使用allocator_分配一塊連續(xù)空間用以存儲(chǔ)元素,其中num_elements()返回的就是數(shù)組各維度的大小的乘積,即數(shù)組的總元素個(gè)數(shù)。分配完之后,就將首指針賦給表述數(shù)組基地址的成員base_。至此multi_array的構(gòu)造工作終于大功告成了。
?
一致性界面——GP的靈魂
multi_array的另一重要特性就是以支持與內(nèi)建多維數(shù)組相同的訪問(wèn)方式,即是說(shuō),multi_array支持以連續(xù)的operator[]來(lái)訪問(wèn)數(shù)組元素。就以(1-3)處的賦值語(yǔ)句為例,讓我們看看multi_array是如何支持這種與內(nèi)建數(shù)組兼容的訪問(wèn)方式的。
?
// multi_array_ref.hpp
//?使用operator[]來(lái)訪問(wèn)元素
reference?operator[](index idx) {
?????return?super_type::access(boost::type<reference>(),
???????????????????idx,origin(),this->shape(),this->strides(),
???????????????????this->index_bases());
}
?
這個(gè)調(diào)用轉(zhuǎn)入了value_accessor_n::access(...)之中:
?
?
// base.hpp
// in class value_accessor_n
template?<typename?Reference,?typename?TPtr>
Reference?access(boost::type<Reference>,
???????????????????index idx,TPtr base,const?size_type* extents,
???????????????????const?index* strides,const?index* index_base)
{
???????TPtr newbase = base + idx * strides[0];
???????return?Reference(newbase,extents+1,
???????????????????????strides+1,index_base+1);
}
?
????這個(gè)連續(xù)調(diào)用operator[]的過(guò)程和extend_gen是很類(lèi)似的——每調(diào)用一層就返回一個(gè)“proxy”,然后在其上繼續(xù)調(diào)用operator[],如此往復(fù)...
那么如果以A[x1][x2][x3]方式訪問(wèn)A中的元素,就相當(dāng)于
A.operator[x1].operator[x2].operator[x3] //連續(xù)調(diào)用“[]”
這三次operator[]調(diào)用返回的類(lèi)型依次為:
sub_array<T,2>?->?sub_array<T,1>?-> T&
????注意,最后一次調(diào)用“[]”返回的恰好是對(duì)元素的引用(這就剛好證明了前面說(shuō)的,只有“[]”的個(gè)數(shù)和數(shù)組的維數(shù)相同的時(shí)候,才能夠取出元素,否則你得到的要么sub_array<...>,要么會(huì)由于試圖在T&上繼續(xù)調(diào)用“[]”而編譯失敗)那么,這一切究竟是如何做到的呢?
?
sub_array的秘密
sub_array的定義在sub_array.hpp中:
?
// sub_array.hpp
template?<typename?T, std::size_t NumDims>
class?sub_array :?public?const_sub_array<T,NumDims,T*>;
?
template?<typename?T, std::size_t NumDims,?typename?TPtr>
class?const_sub_array?:
?????????public?multi_array_impl_base<T,NumDims>;
//base.hpp
template <typename T, std::size_t NumDims>
class?multi_array_impl_base:public
?value_accessor_generator<T,mpl::size_t<NumDims> >::type?;
?
唔,原來(lái)sub_array最終繼承自multi_array_impl_base,后者的基類(lèi)是value_accessor_generator中的一個(gè)typedef,會(huì)根據(jù)NumDims的不同而成為不同的類(lèi)型:
?
// base.hpp
template?<typename?T,?typename?NumDims>
struct?value_accessor_generator?{
?????... ...
?????typedef?typename??//如果NumDims為1,則類(lèi)型為value_accessor_one
?????mpl::apply_if_c<(dimensionality ==?1),
???????????choose_value_accessor_one<T>,
???????????choose_value_accessor_n<T,dimensionality>
??????>::type type;?//把這個(gè)類(lèi)型作為multi_array_impl_base的基類(lèi)!
};
?
很顯然,如果dimensionality ==?1,那么“::type”就是value_accessor_one<T>。而只有對(duì)value_accessor_one使用“[]”才能返回T&,否則,“::type”被推導(dǎo)為:value_accessor_n,這只是個(gè)“proxy”而已,對(duì)它運(yùn)用“[]”只會(huì)返回sub_array<T,NumDims-1>,從而繼續(xù)這個(gè)連續(xù)調(diào)用“[]”的過(guò)程。
?
取出元素
????根據(jù)上面的分析,取元素的任務(wù)最終交給value_accessor_one,其成員函數(shù)access如下:
?????template?<typename?Reference,?typename?TPtr>
?????Reference?access(boost::type<Reference>,index idx,TPtr base,
???????????????????const?size_type*,const?index* strides,
???????????????????const?index*)?const?{
?????????return?*(base + idx * strides[0]); //終于取出了數(shù)據(jù)!
?????}
?
這里,access的返回類(lèi)型Reference就是T&,即數(shù)組中元素類(lèi)型的引用,從而可以將指定元素的引用返回,達(dá)到訪問(wèn)數(shù)組元素的目的。看到這里,multi_array以內(nèi)建數(shù)組訪問(wèn)方式訪問(wèn)數(shù)組元素的過(guò)程基本已經(jīng)弄清楚了,至于其中一些細(xì)節(jié),尤其是計(jì)算地址的細(xì)節(jié),譬如:偏移量的計(jì)算、步長(zhǎng)的使用等,皆已略去了。
現(xiàn)在也許你會(huì)有這樣的疑惑:以內(nèi)建數(shù)組訪問(wèn)方式訪問(wèn)數(shù)組元素的能力真的如此重要嗎?費(fèi)這么大力氣、寫(xiě)這么多代碼還不如以多參數(shù)的方式重載operator[]呢!這么大代價(jià)真的值得嗎?值得!這是勿庸置疑的。以內(nèi)建數(shù)組訪問(wèn)方式訪問(wèn)數(shù)組元素的能力最重要的表現(xiàn)就是,可以使使用者以與內(nèi)建數(shù)組一致的方式對(duì)待multi_array。舉個(gè)例子:用戶編寫(xiě)了一個(gè)函數(shù)模板,
?
template?<typename?ReturnType,?typename?_3DArray>
ReturnType func(_3Array& mda){//可以作用于內(nèi)建多維數(shù)組
... ...
?????mda[x][y][z] = mda[z][x][y];
???... ...
}
?
因?yàn)橛辛艘詢?nèi)建數(shù)組訪問(wèn)方式訪問(wèn)數(shù)組元素的能力,這個(gè)func模板可以同時(shí)應(yīng)用在內(nèi)建數(shù)組和multi_array上(否則用戶就得為multi_array提供一個(gè)單獨(dú)的重載版本),如此一來(lái),代碼的可重用性、可擴(kuò)展性都大大提高了。
?
效率
效率是C++永恒的主題,MultiArray庫(kù)也不例外。執(zhí)行時(shí)間效率上,縱觀MultiArray庫(kù)對(duì)數(shù)組元素的訪問(wèn)代碼,雖然函數(shù)調(diào)用嵌套層數(shù)甚多,但多數(shù)調(diào)用都是簡(jiǎn)單的轉(zhuǎn)發(fā),在現(xiàn)在高度成熟的C++編譯器下,這些轉(zhuǎn)發(fā)的函數(shù)調(diào)用代碼應(yīng)該可以很輕易地被優(yōu)化掉,所以在效率上幾乎沒(méi)有什么損失。在空間效率方面,由于大量運(yùn)用模板技術(shù),基本能夠在編譯期決定的內(nèi)容都已決定,沒(méi)有為運(yùn)行期帶來(lái)不必要的空間上的負(fù)擔(dān)。總的看來(lái),Boost.MultiArray庫(kù)的確是難得的高效又通用的多維數(shù)組的實(shí)現(xiàn)。
?
結(jié)語(yǔ)
本文只是將multi_array最基本的功能代碼做了一個(gè)扼要的分析,正如文章開(kāi)始所說(shuō),multi_array還有許多很有用的特性,如果讀者想充分了解multi_array的運(yùn)作機(jī)制與實(shí)現(xiàn)技巧,就請(qǐng)深入完整地分析multi_array的代碼吧,相信一定會(huì)大有收獲的!
?
目錄(展開(kāi)《boost源碼剖析》系列文章)
總結(jié)
以上是生活随笔為你收集整理的boost源码剖析之:boost::multi_array的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: boost源码剖析之:泛型编程精灵typ
- 下一篇: boost源码剖析之:Tuple Typ