《Effective C 》 條款44 條款45

2021-06-29 02:26:03 字數 4648 閱讀 1881

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