templates(模板)是節(jié)省時(shí)間和避免代碼重復(fù)的極好方法。不必再輸入20個(gè)相似的 classes,每一個(gè)包含 15 個(gè) member functions(成員函數(shù)),你可以輸入一個(gè) class template(類模板),并讓編譯器實(shí)例化出你需要的 20 個(gè) specific classes(特定類)和 300 個(gè)函數(shù)。 (class template(類模板)的 member functions(成員函數(shù))只有被使用時(shí)才會(huì)被隱式實(shí)例化,所以只有在每一個(gè)函數(shù)都被實(shí)際使用時(shí),你才會(huì)得到全部 300 個(gè)member functions(成員函數(shù))。)function templates(函數(shù)模板)也有相似的魅力。不必再寫很多函數(shù),你可以寫一個(gè) function templates(函數(shù)模板)并讓編譯器做其余的事。這不是很重要的技術(shù)嗎?
你的主要工具有一個(gè)有氣勢的名字 commonality and variability analysis(通用性與可變性分析),但是關(guān)于這個(gè)想法并沒有什么有氣勢的東西。即使在你的職業(yè)生涯中從來沒有使用過模板,你也應(yīng)該從始至終做這樣的分析。
當(dāng)你寫一個(gè)函數(shù),而且你意識到這個(gè)函數(shù)的實(shí)現(xiàn)的某些部分和另一個(gè)函數(shù)的實(shí)現(xiàn)本質(zhì)上是相同的,你會(huì)僅僅復(fù)制代碼嗎?當(dāng)然不。你從這兩個(gè)函數(shù)中分離出通用的代碼,放到第三個(gè)函數(shù)中,并讓那兩個(gè)函數(shù)來調(diào)用這個(gè)新的函數(shù)。也就是說,你分析那兩個(gè)函數(shù)以找出那些通用和變化的構(gòu)件,你把通用的構(gòu)件移入一個(gè)新的函數(shù),并把變化的構(gòu)件保留在原函數(shù)中。類似地,假如你寫一個(gè) class,而且你意識到這個(gè) class 的某些構(gòu)件和另一個(gè) class 的構(gòu)件是相同的,你不要復(fù)制那些通用構(gòu)件。作為替代,你把通用構(gòu)件移入一個(gè)新的 class 中,然后你使用 inheritance(繼續(xù))或 composition(復(fù)合)使得原來的 classes 可以訪問這些通用特性。原來的 classes 中不同的構(gòu)件——變化的構(gòu)件——仍保留在它們原來的位置。
template<typename T, // template for n x n matrices of std::size_t n> // objects of type T; see below for info class SquareMatrix { // on the size_t parameter public: ... void invert(); // invert the matrix in place }; 這個(gè) template(模板)取得一個(gè) type parameter(類型參數(shù))T,但是它還有一個(gè)類型為 size_t 的參數(shù)——一個(gè) non-type parameter(非類型參數(shù))。non-type parameter(非類型參數(shù))比 type parameter(類型參數(shù))更不通用,但是它們是完全合法的,而且,就像在本例中,它們可以非常自然。
template<typename T> // size-independent base class for class SquareMatrixBase { // square matrices PRotected: ... void invert(std::size_t matrixSize); // invert matrix of the given size ... };
template< typename T, std::size_t n> class SquareMatrix: private SquareMatrixBase<T> { private: using SquareMatrixBase<T>::invert; // avoid hiding base version of // invert; see Item 33 public: ... void invert() { this->invert(n); } // make inline call to base class }; // version of invert; see below // for why "this->" is here 就像你能看到的,invert 的參數(shù)化版本是在一個(gè) base class(基類)SquareMatrixBase 中的。與 SquareMatrix 一樣,SquareMatrixBase 是一個(gè) template(模板),但與 SquareMatrix 不一樣的是,它參數(shù)化的僅僅是矩陣中的對象的類型,而沒有矩陣的大小。因此,所有持有一個(gè)給定對象類型的矩陣將共享一個(gè)單一的 SquareMatrixBase class。從而,它們共享 invert 在那個(gè) class 中的版本的單一拷貝。
迄今為止,還不錯(cuò),但是有一個(gè)棘手的問題我們還沒有提及。SquareMatrixBase::invert 怎樣知道應(yīng)操作什么數(shù)據(jù)?它從它的參數(shù)知道矩陣的大小,但是它怎樣知道一個(gè)特定矩陣的數(shù)據(jù)在哪里呢?大概只有 derived class(派生類)才知道這些。derived class(派生類)如何把這些傳達(dá)給 base class(基類)以便于 base class(基類)能夠做這個(gè)轉(zhuǎn)置呢?
template<typename T> class SquareMatrixBase { protected: SquareMatrixBase(std::size_t n, T *pMem) // store matrix size and a : size(n), pData(pMem) {} // ptr to matrix values
private: std::size_t size; // size of matrix T *pData; // pointer to matrix values }; 這樣就是讓 derived classes(派生類)決定如何分配內(nèi)存。某些實(shí)現(xiàn)可能決定直接在 SquareMatrix object 內(nèi)部存儲矩陣數(shù)據(jù):
template<typename T, std::size_t n> class SquareMatrix: private SquareMatrixBase<T> { public: SquareMatrix() // send matrix size and : SquareMatrixBase<T>(n, data) {} // data ptr to base class ...
template<typename T, std::size_t n> class SquareMatrix: private SquareMatrixBase<T> { public: SquareMatrix() // set base class data ptr to null, : SquareMatrixBase<T>(n, 0), // allocate memory for matrix pData(new T[n*n]) // values, save a ptr to the { this->setDataPtr(pData.get()); } // memory, and give a copy of it ... // to the base class
private: boost::scoped_array<T> pData; // see Item 13 for info on }; // boost::scoped_array 無論數(shù)據(jù)存儲在哪里,從膨脹的觀點(diǎn)來看要害的結(jié)果在于:現(xiàn)在 SquareMatrix 的許多——也許是全部—— member functions(成員函數(shù))可以簡單地 inline 調(diào)用它的 base class versions(基類版本),而這個(gè)版本是與其它所有持有相同數(shù)據(jù)類型的矩陣共享的,而無論它們的大小。與此同時(shí),不同大小的 SquareMatrix objects 是截然不同的類型,所以,例如,即使 SquareMatrix<double, 5> 和 SquareMatrix<double, 10> objects 使用 SquareMatrixBase<double> 中同樣的 member functions(成員函數(shù)),也沒有機(jī)會(huì)將一個(gè) SquareMatrix<double, 5> object 傳送給一個(gè)期望一個(gè) SquareMatrix<double, 10> 的函數(shù)。很好,不是嗎?
另一方面,將唯一的 invert 的版本用于多種矩陣大小縮小了可執(zhí)行碼的大小,而且還能縮小程序的 working set(工作區(qū))大小以及改善 instruction cache(指令緩存)中的 locality of reference(引用的局部性)。這些能使程序運(yùn)行得更快,超額償還了失去的針對 invert 的 size-specific versions(特定大小版本)的任何優(yōu)化。哪一個(gè)效果更劃算?唯一的分辨方法就是在你的特定平臺和典型數(shù)據(jù)集上試驗(yàn)兩種方法并觀察其行為。
另一個(gè)效率考慮關(guān)系到 objects 的大小。假如你不小心,將函數(shù)的 size-independent 版本(大小無關(guān)版本)上移到一個(gè) base class(基類)中會(huì)增加每一個(gè) object 的整體大小。例如,在我剛才展示的代碼中,即使每一個(gè) derived class(派生類)都已經(jīng)有了一個(gè)取得數(shù)據(jù)的方法,每一個(gè) SquareMatrix object 都還有一個(gè)指向它的數(shù)據(jù)的指針存在于 SquareMatrixBase class 中,這為每一個(gè) SquareMatrix object 至少增加了一個(gè)指針的大小。通過改變設(shè)計(jì)使這些指針不再必需是有可能的,但是,這又是一樁交易。例如,讓 base class(基類)存儲一個(gè)指向矩陣數(shù)據(jù)的 protected 指針導(dǎo)致封裝性的降低。它也可能導(dǎo)致資源治理復(fù)雜化:假如 base class(基類)存儲了一個(gè)指向矩陣數(shù)據(jù)的指針,但是那些數(shù)據(jù)既可以是動(dòng)態(tài)分配的也可以是物理地存儲于 derived class object(派生類對象)之內(nèi)的(就像我們看到的),它如何決定這個(gè)指針是否應(yīng)該被刪除?這樣的問題有答案,但是你越想讓它們更加精巧一些,它就會(huì)變成更復(fù)雜的事情。 在某些條件下,少量的代碼重復(fù)就像是一種解脫。
本文只討論了由于 non-type template parameters(非類型模板參數(shù))引起的膨脹,但是 type parameters(類型參數(shù))也能導(dǎo)致膨脹。例如,在很多平臺上,int 和 long 有相同的二進(jìn)制表示,所以,可以說,vector<int> 和 vector<long> 的 member functions(成員函數(shù))很可能是相同的——膨脹的恰到好處的解釋。某些連接程序會(huì)合并同樣的函數(shù)實(shí)現(xiàn),還有一些不會(huì),而這就意味著在一些環(huán)境上一些模板在 int 和 long 上都被實(shí)例化而能夠引起代碼重復(fù)。類似地,在大多數(shù)平臺上,所有的指針類型有相同的二進(jìn)制表示,所以持有指針類型的模板(例如,list<int*>,list<const int*>,list<SquareMatrix<long, 3>*> 等)應(yīng)該通常可以使用每一個(gè) member function(成員函數(shù))的單一的底層實(shí)現(xiàn)。典型情況下,這意味著與 strongly typed pointers(強(qiáng)類型指針)(也就是 T* 指針)一起工作的 member functions(成員函數(shù))可以通過讓它們調(diào)用與 untyped pointers(無類型指針)(也就是 void* 指針)一起工作的函數(shù)來實(shí)現(xiàn)。一些標(biāo)準(zhǔn) C++ 庫的實(shí)現(xiàn)對于像 vector,deque 和 list 這樣的模板就是這樣做的。假如你關(guān)心起因于你的模板的代碼膨脹,你可能需要用同樣的做法開發(fā)模板。
·non-type template parameters(非類型模板參數(shù))引起的膨脹經(jīng)常可以通過用 function parameters(函數(shù)參數(shù))或 class data members(類數(shù)據(jù)成員)替換 template parameters(模板參數(shù))而消除。