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