国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

C++箴言:從模板中分離出參數(shù)無關(guān)的代碼

2019-11-17 05:08:48
字體:
供稿:網(wǎng)友

  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ù)嗎?

  是的,不錯(cuò)……有時(shí)。假如你不小心,使用 templates(模板)可能導(dǎo)致 code bloat(代碼膨脹):重復(fù)的(或幾乎重復(fù)的)的代碼,數(shù)據(jù),或兩者都有的二進(jìn)制碼。結(jié)果會(huì)使源代碼看上去緊湊而整潔,但是目標(biāo)代碼臃腫而松散。臃腫而松散很少會(huì)成為時(shí)尚,所以你需要了解如何避免這樣的二進(jìn)制擴(kuò)張。

  你的主要工具有一個(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)件——仍保留在它們原來的位置。

  在寫 templates(模板)時(shí),你要做同樣的分析,而且用同樣的方法避免重復(fù),但這里有一個(gè)技巧。在 non-template code(非模板代碼)中,重復(fù)是顯式的:你可以看到兩個(gè)函數(shù)或兩個(gè)類之間存在重復(fù)。在 template code(模板代碼)中。重復(fù)是隱式的:僅有一份 template(模板)源代碼的拷貝,所以你必須培養(yǎng)自己去判定在一個(gè) template(模板)被實(shí)例化多次后可能發(fā)生的重復(fù)。

  例如,假設(shè)你要為固定大小的 square matrices(正方矩陣)寫一個(gè) templates(模板),其中,要支持 matrix inversion(矩陣轉(zhuǎn)置)。

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ù))更不通用,但是它們是完全合法的,而且,就像在本例中,它們可以非常自然。

  現(xiàn)在考慮以下代碼:

SquareMatrix<double, 5> sm1;
...
sm1.invert(); // call SquareMatrix<double, 5>::invert

SquareMatrix<double, 10> sm2;
...
sm2.invert(); // call SquareMatrix<double, 10>::invert
  這里將有兩個(gè) invert 的拷貝被實(shí)例化。這兩個(gè)函數(shù)不是相同的,因?yàn)橐粋€(gè)作用于 5 x 5 矩陣,而另一個(gè)作用于 10 x 10 矩陣,但是除了常數(shù) 5 和 10 以外,這兩個(gè)函數(shù)是相同的。這是一個(gè)發(fā)生 template-indUCed code bloat(模板導(dǎo)致的代碼膨脹)的經(jīng)典方法。

  假如你看到兩個(gè)函數(shù)除了一個(gè)版本使用了 5 而另一個(gè)使用了 10 之外,對應(yīng)字符全部相等,你該怎么做呢?你的直覺讓你創(chuàng)建一個(gè)取得一個(gè)值作為一個(gè)參數(shù)的函數(shù)版本,然后用 5 或 10 調(diào)用這個(gè)參數(shù)化的函數(shù)以代替復(fù)制代碼。你的直覺為你提供了很好的方法!以下是一個(gè)初步過關(guān)的 SquareMatrix 的做法:

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 中的版本的單一拷貝。

  SquareMatrixBase::invert 僅僅是一個(gè)計(jì)劃用于 derived classes(派生類)以避免代碼重復(fù)的方法,所以它是 protected 的而不是 public 的。調(diào)用它的額外成本應(yīng)該為零,因?yàn)?derived classes(派生類)的 inverts 使用 inline functions(內(nèi)聯(lián)函數(shù))調(diào)用 base class(基類)的版本。(這個(gè) inline 是隱式的——參見《理解inline化的介入和排除》。)這些函數(shù)使用了 "this->" 標(biāo)記,因?yàn)榫拖?Item 43 解釋的,假如不這樣,在 templatized base classes(模板化基類)中的函數(shù)名(諸如 SquareMatrixBase<T>)被 derived classes(派生類)隱藏。還要注重 SquareMatrix 和 SquareMatrixBase 之間的繼續(xù)關(guān)系是 private 的。這準(zhǔn)確地反映了 base class(基類)存在的理由僅僅是簡化 derived classes(派生類)的實(shí)現(xiàn)的事實(shí),而不是表示 SquareMatrix 和 SquareMatrixBase 之間的一個(gè)概念上的 is-a 關(guān)系。(關(guān)于 private inheritance(私有繼續(xù))的信息,參見 《謹(jǐn)慎使用私有繼續(xù)》。)

  迄今為止,還不錯(cuò),但是有一個(gè)棘手的問題我們還沒有提及。SquareMatrixBase::invert 怎樣知道應(yīng)操作什么數(shù)據(jù)?它從它的參數(shù)知道矩陣的大小,但是它怎樣知道一個(gè)特定矩陣的數(shù)據(jù)在哪里呢?大概只有 derived class(派生類)才知道這些。derived class(派生類)如何把這些傳達(dá)給 base class(基類)以便于 base class(基類)能夠做這個(gè)轉(zhuǎn)置呢?

  一種可能是為 SquareMatrixBase::invert 增加另一個(gè)的參數(shù),也許是一個(gè)指向存儲矩陣數(shù)據(jù)的內(nèi)存塊的開始位置的指針。這樣可以工作,但是十有八九,invert 不是 SquareMatrix 中僅有的能被寫成一種 size-independent(大小無關(guān))的方式并移入 SquareMatrixBase 的函數(shù)。假如有幾個(gè)這樣的函數(shù),全都需要一種找到持有矩陣內(nèi)的值的內(nèi)存的方法。我們可以為它們?nèi)荚黾右粋€(gè)額外的參數(shù),但是我們一再重復(fù)地告訴 SquareMatrixBase 同樣的信息。這看上去不太正常。

  一個(gè)可替換方案是讓 SquareMatrixBase 存儲一個(gè)指向矩陣的值的內(nèi)存區(qū)域的指針。而且一旦它存儲了這個(gè)指針,它同樣也可以存儲矩陣大小。最后得到的設(shè)計(jì)大致就像這樣:


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

void setDataPtr(T *ptr) { pData = ptr; } // reassign pData
...

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
...

private:
T data[n*n];
};
  這種類型的 objects 不需要 dynamic memory allocation(動(dòng)態(tài)內(nèi)存分配),但是這些 objects 本身可能會(huì)非常大。一個(gè)可選方案是將每一個(gè)矩陣的數(shù)據(jù)放到 heap(堆)上:

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ù)。很好,不是嗎?

  很好,是的,但不是免費(fèi)的。將矩陣大小硬性固定在其中的 invert 版本很可能比將大小作為一個(gè)函數(shù)參數(shù)傳入或存儲在 object 中的共享版本能產(chǎn)生更好的代碼。例如,在 size-specific(特定大小)的版本中,sizes(大小)將成為 compile-time constants(編譯期常數(shù)),因此適用于像 constant propagation 這樣的優(yōu)化,包括將它們作為 immediate Operands(立即操作數(shù))嵌入到生成的指令中。在 size-independent version(大小無關(guān)版本)中這是不可能做到的。

  另一方面,將唯一的 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ā)模板。

  Things to Remember

  ·templates(模板)產(chǎn)生多個(gè) classes 和多個(gè) functions,所以一些不依靠于 template parameter(模板參數(shù))的模板代碼會(huì)引起膨脹。

  ·non-type template parameters(非類型模板參數(shù))引起的膨脹經(jīng)常可以通過用 function parameters(函數(shù)參數(shù))或 class data members(類數(shù)據(jù)成員)替換 template parameters(模板參數(shù))而消除。

  ·type parameters(類型參數(shù))引起的膨脹可以通過讓具有相同的二進(jìn)制表示的實(shí)例化類型共享實(shí)現(xiàn)而減少

發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 定兴县| 轮台县| 黄浦区| 甘南县| 共和县| 定安县| 诏安县| 延寿县| 东城区| 五华县| 珲春市| 永兴县| 河西区| 揭西县| 慈溪市| 岳普湖县| 杭锦后旗| 太原市| 平度市| 芷江| 富民县| 邓州市| 太谷县| 浠水县| 宁夏| 凤庆县| 南华县| 大厂| 东乌珠穆沁旗| 霸州市| 和龙市| 辽中县| 沭阳县| 广宗县| 图片| 东方市| 扎赉特旗| 项城市| 渭南市| 义乌市| 林口县|