templates可以節省時間和避免**重複。對於類似的classes或functions,可以寫乙個class template或function template,讓編譯器來做剩餘的事。這樣做,有時候會導致**膨脹(code bloat):其二進位製碼帶著重複(或幾乎重複)的**、資料,或者兩者。但這時候源**看起來可能很整齊。
先來學習乙個名詞:共性與變性分析(commonality and variability analysis)。比較容易理解。例如,你在編寫幾個函式,會用到相同作用的**;這時候你往往將相同**搬到乙個新函式中,給其他幾個函式呼叫。同理,如果編寫某個class,其中某些部分和另外幾個class相同,這時候你不會重複編寫這些相同部分,只需把共同部分搬到新class中去即可,去使用繼承或復合(**條款**32,38,39),讓原先的classes取用這些共同特性,原classes的互異部分(變異部分)仍然留在原位置不動。
編寫templates時,也要做相同分析,避免重複。non-template**中重複十分明確:你可以看到兩個函式或classes之間有所重複。但是在template**中,重複是隱晦的,因為只有乙份template原始碼。
例如,你打算在為尺寸固定的正方矩陣編寫乙個template,該矩陣有個支援逆矩陣運算的函式
template
//t為資料型別,n為矩陣大小
class squarematrix;
squarematrix sm1;
sm1.invert();//呼叫squarematrix::invert
squarematrix sm2;
sm2.invert();//呼叫squarematrix::invert
上面會具體化兩份invert。這兩份函式幾乎完全相同(除了乙個操作5*5矩陣,乙個操作10*10)。這就是**膨脹的乙個典型例子。
上面兩個函式除了操作矩陣大小不同外,其他相同。這時可以為其建立乙個帶數值的函式,而不是重複**。於是有了對squarematrix的第乙份修改:
template
class squarematrixbase;
template
class squarematrix:private squarematrixbase
};
squarematrixbase::invert只是企圖避免derived classes**重複,所以它以protected替換public。這個函式使用this->,因為模板化基類內的函式名稱會被derived classes掩蓋(條款**43)。注意,squarematrixbase和squarematrix之間繼承關係是private,這說明base class是為了幫助derived classes實現,兩者不是**is-a關係。
現在還有乙個問題,squarematrixbase::invert操作的資料在哪?它在引數中直到矩陣大小,但是矩陣資料derived class才知道。derived class和base class如何聯絡?乙個做法是可以為squarematrixbase::invert新增乙個引數(例如乙個指標)。這個行得通,但是考慮到其他因素(例如,squarematrixbase內還有其他函式,也要操作這些資料),可以把這個指標新增到squarematrixbase類中。
templateclass squarematrixbase
void setdataptr(t* ptr)
……private:
std::size_t size;
t* pdata;
};templateclass squarematrix:private squarematrixbase
……private:
t data[n*n];
};
這種型別的物件不需要動態分配記憶體,但是物件自身可能非常大。另乙個做法是把矩陣資料放到heap
templatet, std:
:size_t n>
class
squarematrix:private
squarematrixbase
>
……private:
boost:
:scoped_array pdata;
};
這樣以來,型別相同的derived classes會共享base class。例如,squarematrix
在模板中,具體化模板引數後的類不會因為具體化型別而存在派生關係。來看乙個關於指標的例子。真實指標支援隱式轉換(implitic conversions);derived class指標可以隱式轉換為base class指標,指向non-const物件的指標可以轉換為指向const物件的指標,等等。例如:
class top
; class middle: public top
; class bottom:public middle;
top* pt1=new middle;//middle* 轉換為top*
top* pt2=new bottom;//bottom* 轉換為top*
const top* pct2=pt1;//top* 轉換為const top*
如果使用模板定義智慧型指標,上面的轉換就有點麻煩了
template
class smartprt;
smartptrpt1=smartptr(new middle);//smartptr轉換為smartptr
smartprtpt2=smartprt(new bottom);
smartprt pct2=pt1;
同乙個template的不同具體化之間不存在什麼關係,即使具體化的兩個型別之間有繼承、派生關係。編譯器把smartptr和smartptr視為完全不同兩種型別的classes。為了讓上面**編譯通過,獲得smartptr classes之間的轉換能力,必須明確的把它們編寫出來。
要想實現轉換,可以在智慧型指標的建構函式中完成,但是如果派生類有繼續派生,那麼建構函式又要新增,這顯然不合理。因此,我們需要的不是簡簡單單為smartptr寫建構函式,而是編乙個構造模板。這麼的模板是所謂的member function template(簡稱member templates),作用是為class生成函式
template
class smartprt;
以上**意思是,對任何型別t和任何型別u,可以根據smartprt生成乙個smartptr。copy cotr沒有宣告為explicit,因為轉換可能是隱式的。
這個為smartptr而寫的泛化建構函式提供的東西比我們需要的更多。我們希望根據乙個smartptr建立乙個smartprt,卻不希望根據乙個smartptr建立乙個smartptr,因為對於public繼承來說是矛盾的。
上述**並不完整,在smartptr沒有實現copy cotr。假設smartptr像auto_ptr和tr1::shared_ptr一樣,提供get成員函式,返回智慧型指標物件,那麼就可以在構造模板中約束轉換行為
templateclass smartptr;
t* get() const
……private:
t* heldprt;
};
在上述**中,存乙個隱式轉換:將u* 轉換為 t*,這限制了轉換行為。
member function templates作用不僅僅在於建構函式,還有乙個重要作用是支援賦值操作。例如tr1的shared_ptr支援所有來自相容之內置指標、tr1::shared_ptrs、auto_ptrs和tr1::weak_ptrs的構造行為,以及來自上述各物(tr1::weak_ptr除外)的賦值操作。來看一下tr1規範中關於tr1::shared_ptr的乙份摘錄
template
class
shared_ptr;
上面除了泛化copy建構函式外,其他建構函式都是explicit,表示shared_ptr型別隱式轉換被允許,但是從其他智慧型指標隱式轉換為shared_ptr不被允許。
member function templates並不改變語言基本規則,和編譯器產生copy建構函式以及copy assignment不衝突。tr1:shared_ptr宣告了乙個泛化的copy建構函式,如果t和y相同,泛化的copy建構函式會被具體化為正常的copy建構函式。編譯器會暗自為tr1::shared_ptr生成乙個copy建構函式?還是當tr1::shared_ptr物件根據另乙個同型別的tr1::shared_ptr物件展開構造行為時,編譯器會將泛化的copy建構函式模板具體化呢?
member templates沒有改變語言規則,如果程式需要乙個copy建構函式,你卻沒有宣告它,編譯器就會替你生成。在class內宣告泛化copy建構函式並不阻止編譯器生成它們自己的copy建構函式(non-template)。如果想要控制copy建構函式的方方面面,就要宣告正常的copy建構函式。相同的規則也適用於賦值assignment操作。
總結
- 請使用member function templates(成員函式模板)生成「可接受所有相容型別」的函式。
- 如果宣告member templates用於泛化copy建構函式或泛化assignment操作,還是要宣告正常的copy建構函式和copy assignment操作符。
Effective C 經驗條款
高效c 4 必須返回物件時,別妄想返回其reference 這句話什麼意思呢?就是在乙個函式內,如果你需要這個函式返回乙個新的物件,那麼這個函式的返回值型別就不要定義成引用型別。就直接返回這個類型別。首先,我們知道在函式傳遞引數時,傳遞引用的好處,尤其是對那麼比較大的型別,但是對於內建型別和stl的...
effective c 條款總結
條款1 盡量用const 和inline 而不用 define 條款2 盡量用而不用 條款3 盡量用new delete 而不用malloc free 條款4 盡量使用c 風格注釋 條款5 對應的new和delete 都要採用相同的形式 條款6 析構函式裡對指標成員呼叫delete條款 條款7 預先...
Effective C 經驗條款
高效c 模板與泛型程式設計 在c 中模板體現的是編譯期多型,virtual體現的是執行期多型。關於typename的雙重含義 在宣告template引數時,不論使用keywordclass或typename,意義全然同樣。可是c 並不總是把class和typename視為等價。有時候必須使用type...