[翻譯] Effective C++, 3rd Edition, Item 44: 從 templates(模板)中分離出 parameter-independent(參數無關)的代碼(上)

Item 44: 從 templates(模板)中分離出 parameter-independent(參數無關)的代碼

作者:Scott Meyers

譯者:fatalerror99 (iTePub's Nirvana)

發佈:http://blog.csdn.net/fatalerror99/

templates(模板)是節省時間和避免代碼重複的極好方法。不必再輸入 20 個相似的 classes,每一個包含 15 個 member functions(成員函數),你可以輸入一個 class template(類模板),並讓編譯器實例化出你需要的 20 個 specific classes(特定類)和 300 個函數。(class template(類模板)的 member functions(成員函數)只有被使用時纔會被隱式實例化,所以只有在每一個函數都被實際使用時,你纔會得到全部 300 個 member functions(成員函數)。)function templates(函數模板)也有相似的魅力。不必再寫很多函數,你可以寫一個 function templates(函數模板)並讓編譯器做其餘的事。這不是很重要的技術嗎?

是的,不錯……有時。如果你不小心,使用 templates(模板)可能導致 code bloat(代碼膨脹):重複的(或幾乎重複的)的代碼,數據,或兩者都有的二進制碼。結果會使源代碼看上去緊湊而整潔,但是目標代碼臃腫而鬆散。臃腫而鬆散很少會成爲時尚,所以你需要了解如何避免這樣的二進制擴張。

你的主要工具有一個高高在上的名字 commonality and variability analysis(通用性與可變性分析),但是這個想法本身並沒有那麼高高在上。即使在你的職業生涯中從來沒有使用過模板,你也應該從始至終做這樣的分析。

當你寫一個函數,而且你意識到這個函數的實現的某些部分和另一個函數的實現本質上是相同的,你會僅僅複製代碼嗎?當然不。你從這兩個函數中分離出通用的代碼,放到第三個函數中,並讓那兩個函數來調用這個新的函數。也就是說,你分析那兩個函數以找出那些通用和變化的構件,你把通用的構件移入一個新的函數,並把變化的構件保留在原函數中。類似地,如果你寫一個 class,而且你意識到這個 class 的某些構件和另一個 class 的構件是相同的,你不要複製那些通用構件。作爲替代,你把通用構件移入一個新的 class 中,然後你使用 inheritance(繼承)或 composition(複合)(參見 Items 323839)使得原來的 classes 可以訪問這些通用特性。原來的 classes 中不同的構件——變化的構件——仍保留在它們原來的位置。

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

例如,假設你要爲固定大小的 square matrices(正方矩陣)寫一個 templates(模板),其中,要支持 matrix inversion(矩陣求逆)。

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

這個 template(模板)取得一個 type parameter(類型參數)T,但是它還有一個類型爲 size_t 的參數——一個 non-type parameter(非類型參數)。non-type parameter(非類型參數)比 type parameter(類型參數)更不通用,但是它們是完全合法的,而且,就像在本例中,它們可以非常自然。

現在考慮以下代碼:

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

SquareMatrix<double, 10> sm2;
...
sm2.invert();                  // call SquareMatrix<double, 10>::invert

這裏將有兩個 invert 的拷貝被實例化。這兩個函數是不相同的,因爲一個作用於 5 x 5 矩陣,而另一個作用於 10 x 10 矩陣,但是除了常數 5 和 10 以外,這兩個函數是相同的。這是一個發生 template-induced code bloat(模板導致的代碼膨脹)的經典方法。

如果你看到兩個函數除了一個版本使用了 5 而另一個使用了 10 之外,對應字符全部相等,你該怎麼做呢?你的直覺讓你創建一個取得一個值作爲一個參數的函數版本,然後用 5 或 10 調用這個參數化的函數以代替複製代碼。你的直覺爲你提供了很好的方法!以下是一個初步過關的 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 的參數化版本是在一個 base class(基類)SquareMatrixBase 中的。與 SquareMatrix 一樣,SquareMatrixBase 是一個 template(模板),但與 SquareMatrix 不一樣的是,它參數化的僅僅是矩陣中的對象的類型,而沒有矩陣的大小。因此,所有持有一個給定對象類型的矩陣將共享一個單一的 SquareMatrixBase class。從而,它們共享 invert 在那個 class 中的版本的單一拷貝。

SquareMatrixBase::invert 僅僅是一個計劃用於 derived classes(派生類)以避免代碼重複的方法,所以它是 protected 的而不是 public 的。調用它的額外成本應該爲零,因爲 derived classes(派生類)的 inverts 使用 inline functions(內聯函數)調用 base class(基類)的版本。(這個 inline 是隱式的——參見 Item 30。)這些函數使用了 "this->" 標記,因爲就像 Item 43 解釋的,如果不這樣,在 templatized base classes(模板化基類)中的函數名(諸如 SquareMatrixBase<T>)被 derived classes(派生類)隱藏。還要注意 SquareMatrixSquareMatrixBase 之間的繼承關係是 private 的。這準確地反映了 base class(基類)存在的理由僅僅是簡化 derived classes(派生類)的實現的事實,而不表示 SquareMatrixSquareMatrixBase 之間的一個概念上的 is-a 關係。(關於 private inheritance(私有繼承)的信息,參見 Item 39。)

迄今爲止,還不錯,但是有一個棘手的問題我們還沒有提及。SquareMatrixBase::invert 怎樣知道應操作什麼數據?它從它的參數知道矩陣的大小,但是它怎樣知道一個特定矩陣的數據在哪裏呢?大概只有 derived class(派生類)才知道這些。derived class(派生類)如何把這些傳達給 base class(基類)以便於 base class(基類)能夠做這個轉置呢?

一種可能是爲 SquareMatrixBase::invert 增加另一個的參數,也許是一個指向存儲矩陣數據的內存塊的開始位置的指針。這樣可以工作,但是十有八九,invert 不是 SquareMatrix 中僅有的能被寫成一種 size-independent(大小無關)的方式並移入 SquareMatrixBase 的函數。如果有幾個這樣的函數,全都需要一種找到持有矩陣內的值的內存的方法。我們可以爲它們全都增加一個額外的參數,但是我們一再重複地告訴 SquareMatrixBase 同樣的信息。這看上去不太正常。

(未完,點擊此處,接下篇)

發佈了24 篇原創文章 · 獲贊 3 · 訪問量 44萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章