国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
boost源碼剖析之:泛型多維數(shù)組類(lèi)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ù)值上加上一行的大?。ū纠秊?/span>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)??梢栽谡搲峡吹疥P(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表示各維的大?。?/span>n1*n2*n3

     multi_array<int,3> ma ( dim_sizes[n1][n2][n3] );

     ma[i][j][k] = value; //為第i頁(yè)jk列的元素賦值

     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);

extentsextent_gen<0>類(lèi)型的,extents.operator [](3)應(yīng)調(diào)用函數(shù)2-3,此時(shí)NumRange0,而返回類(lèi)型是extent_gen<1>;再以該返回對(duì)象調(diào)用operator [](4),此時(shí)NumRange1,而返回類(lèi)型則是extent_gen<2>了。再看函數(shù)2-3的內(nèi)容,其實(shí)就是將參數(shù)idxrange包裝一下再轉(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)?/span>mult_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ù)自己的需求用iteratorextents中取出它所需要的數(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ǔ)策略?。?/span>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ù)的聲明是與STLcontainer concept完全一致的。所謂與STL的兼容性正是在這里體現(xiàn)出來(lái)了。而const_multi_array_ref更是類(lèi)如其名,const_multi_array_ref中所有訪問(wèn)元素、查詢(xún)數(shù)組信息等成員函數(shù)都返回constreferenceiterator。而反觀multi_array_ref的聲明,其中只比const_multi_array_ref多了訪問(wèn)元素、查詢(xú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 : 提供constSTL數(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  //如果NumDims1,則類(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以?xún)?nèi)建數(shù)組訪問(wèn)方式訪問(wèn)數(shù)組元素的過(guò)程基本已經(jīng)弄清楚了,至于其中一些細(xì)節(jié),尤其是計(jì)算地址的細(xì)節(jié),譬如:偏移量的計(jì)算、步長(zhǎng)的使用等,皆已略去了。

現(xiàn)在也許你會(huì)有這樣的疑惑:以?xún)?nèi)建數(shù)組訪問(wèn)方式訪問(wèn)數(shù)組元素的能力真的如此重要嗎?費(fèi)這么大力氣、寫(xiě)這么多代碼還不如以多參數(shù)的方式重載operator[]呢!這么大代價(jià)真的值得嗎?值得!這是勿庸置疑的。以?xún)?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)橛辛艘詢(xú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源碼剖析系列文章)

 

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
c# 復(fù)習(xí)、筆試全攻略(三、結(jié)構(gòu)、枚舉和數(shù)組)
編程中國(guó)
c語(yǔ)言一維數(shù)組做參數(shù)傳遞給函數(shù):
【NumPy學(xué)習(xí)指南】day4 多維數(shù)組的切片和索引
C語(yǔ)言中定義int a[10][10],a是什么類(lèi)型?
Excel數(shù)組和數(shù)組函數(shù)
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服