目錄
- 條款41:了解隱式接口和編譯多態
- 條款42:了解typename的雙重意義
- 條款43:學習處理模板化基類內的名稱
- 條款44:將與參數無關的代碼抽離templates
- 條款45:運用成員函數模板接受所有兼容類型
- 條款46:需要類型轉換時請為模板定義非成員函數
- 條款47:請使用traits classes表現類型信息
- 條款48:認識模板元編程
條款41:了解隱式接口和編譯多態
- 顯示接口:在源碼中可以找到明確可見的函數接口。
- 隱式接口:不是基于函數簽名式,而是有效表達式組成的接口。如下圖,函數接口最終實例化成什么函數和模板參數T有關,在代碼編譯之前是不確定的。
- 運行期多態:在運行期虛函數指針根據指向對象確定哪一個虛函數該被綁定調用。
- 編譯期多態:在編譯期以不同的模板參數具現化會導致調用不同的函數。
- 對classes而言接口是顯式的(explicit),以函數簽名為中心。多態則是通過 virtual函數發生于運行期。
- 對template參數而言,接口是隱式的(implicit),奠基于有效表達式。多態則是通過template具現化和函數重載解析(function overloading resolution)發生于編譯期。
條款42:了解typename的雙重意義
- 當聲明template類型參數時,class和typename意義完全相同。建議使用typename。
- template內出現的名稱如果相依于某個template參數,稱之為從屬名稱(dependent names)。如果從屬名稱在 class內呈嵌套狀,我們稱它為嵌套從屬名稱(nested dependent name)。
- 當嵌套從屬名稱表示一個類型時,需要加上typename前綴,表示是一個類型,也叫做嵌套從屬類型名稱;如果不加typename前綴,默認這個嵌套從屬名稱是一個非類型(例如某個類的靜態成員變量C::value)。
- “typename必須作為嵌套從屬類型名稱的前綴詞”這一規則的例外是,**typename不可以出現在base classes list內的嵌套從屬類型名稱之前,也不可在member initialization list(成員初值列)中作為base class修飾符。**例如:
- 對于冗長的嵌套從屬類型名稱可用typedef簡化:
條款43:學習處理模板化基類內的名稱
-
C++拒絕這個調用的原因:它知道base class templates有可能被特化,而那個特化版本可能不提供和一般性 template相同的接口。因此它往往拒絕在 templatized base classes(模板化基類,本例的MsgSender)內尋找繼承而來的名稱(本例的 sendClear)。實例化前編譯器不進入base class作用域內查找。就某種意義而言,當我們從Object Oriented C++跨進Template C++,繼承就不像以前那般暢行無阻了。
-
解決方法統一思路:對編譯器承諾“base class template的任何特化版本都將支持其一般(泛化)版本所提供的接口”。
-
解決方法1:在base class 函數調用動作之前加上“this->”:
-
解決方法2:使用using聲明式提前聲明模板基類函數:
-
解決方法3(不推薦):明確指出調用的函數位于模板基類里。若調用的是virtual函數,這種明確修飾會關閉“virtual綁定行為”(只會調用基類的虛函數,而不會調用繼承類中對應重寫的虛函數):
-
編譯器的診斷時間可能發生在早期(當解析derived class template 的定義式時),也可能發生在晚期(當那些templates被特定之template 實參具現化時)。C++的政策是寧愿較早診斷,這就是為什么“當base classes 從templates中被具現化時”
它假設它對那些baseclasses的內容毫無所悉的緣故。
條款44:將與參數無關的代碼抽離templates
- 模板代碼膨脹:其二進制碼帶著重復(或幾乎重復)的代碼、數據,或兩者。其結果有可能源碼看起來合身而整齊,但目標碼(object code) 卻不是那么回事。
- Templates生成多個classes和多個函數,所以任何template代碼都不該與某個造成膨脹的template參數產生相依關系。
- 因非類型模板參數(non- type template parameters) 而造成的代碼膨脹,往往可消除,做法是以函數參數或class成員變量替換template參數。
- 因類型參數(type parameters)而造成的代碼膨脹,往往可降低,做法是讓帶有完全相同二進制表述( binary representations)的具現類型( instantiation types)共享實現碼。
- 有時愈是嘗試精密的做法,事情變得愈是復雜。從某個角度看,一點點代碼重復反倒看起來有點幸運了。需要在具體不同平臺進行衡量。
條款45:運用成員函數模板接受所有兼容類型
- 以智能指針是“行為像指針”的對象,討論如何實現成員函數模板用于接受所有兼容類型,泛化智能指針操作類似指針操作。
- 通過成員函數模板建立一個泛化的copy構造函數,使得該拷貝構造函數可以支持智能指針隱式轉換。這個行為只有當“存在某個隱式轉換可將-一個U指針轉為-一個T指針”時才能通過編譯,而那正是我們想要的。這個構造函數只在其所獲得的實參隸屬適當(兼容)類型時才通過編譯:
- 通過成員函數模板建立一個泛化的賦值操作“=”函數。下圖所有構造函數都是explicit, 惟有“泛化copy構造函數”除外。那意味從某個shared_ ptr 類型隱式轉換至另一個shared ptr 類型是被允許的,但從某個內置指針或從其他智能指針類型進行隱式轉換則不被認可(如果是顯式轉換如cast強制轉型動作倒是可以)。
- member templates 并不改變語言規則,而語言規則說,如果程序需要一個copy構造函數,你卻沒有聲明它,編譯器會為你暗自生成一個。 在class內聲明泛化copy構造函數(是個member template)并不會阻止編譯器生成它們自己的copy構造函數(一個non-template),所以如果你想要控制copy構造的方方面面,你必須同時聲明泛化copy構造函數和“正常的”copy構造函數。相同規則也適用于賦值( assignment)操作。
條款46:需要類型轉換時請為模板定義非成員函數
- 模板中,根據實參推導模板參數類型時不支持隱式類型轉換推導。如果一個模板類中,模板內的函數需要參數類型轉換,成員函數無法做到。函數參數隱式類型轉換之前需要確定該函數存在,而實例化之前成員函數并不存在。
- 可在模板類內中采用friend函數聲明和定義,使其聲明跟隨模板類一起實例化,之后再調用的時候就可以自動找到該函數的聲明,進行隱式類型轉換(下圖中可在*重載中輸入int類型數據,使其隱式調用Rational的int類型構造函數,隱式轉換為Rational<int>)。
- 由于定義于class內中的函數暗自成為inline,可采用class內中friend函數啥也不干,只調用一個外部輔助函數即可,減小inline聲明所帶來的沖擊。
條款47:請使用traits classes表現類型信息
- STL中迭代器可分為五類,繼承關系如下圖:
(1). Input 迭代器,只能一步一步向前移動,只能讀取內容,且僅讀取一次。
(2). Output 迭代器,只能一步一步向前移動,只能寫入內容,且僅寫入一次。
(3). Forward 迭代器,只能一步一步向前移動,可讀可寫多次。
(4). Bidirectianol 迭代器,只能一步一步向前或向后移動,可讀可寫多次。
(5). Random Access 迭代器,可以在常量時間以任意步數向前或向后移動,可讀可寫多次。
-
Traits是一種技術,也是一個C++程序員共同遵守的協議。它對內置類型和用戶自定義類型的表現必須一樣好。
-
Traits設計實現流程(例子是實現advance模板函數,可以接受各種類型的迭代器進行移動):
-(1)確認若干你希望將來可取得的類型信息;
-(2)為該信息選一個名稱;
-(3)提供一個template和一組特例化版本( 例特例化內置指針類型):
-
Traits classes使得“類型相關信息”在編譯期可用。它們以templates和“templates特化”完成實現。整合重載技術( overloading) 后,traitsclasses有可能在編譯期對類型執行if…else測試。(上述advance中有類型條件判單,是在運行期執行,可采用重載技術使其在編譯期進行,僅在advance函數中調用重載函數即可,如下圖)。
- 如何使用一個traits class:
(1)建立一組重載函數 (身份像勞工)或函數模板(例如doAdvance) ,彼此間的差異只在于各自的traits參數。令每個函數實現碼與其接受之traits信息相應和。.
(2)建立一個控制函數 (身份像工頭)或函數模板(例如advance) ,它調用上述那些“勞工函數”并傳遞traits class所提供的信息。
條款48:認識模板元編程
- Template metaprogramming (TMP,模板元編程)可將工作由運行期移往編譯期,因而得以實現早期錯誤偵測和更高的執行效率。
- TMP可被用來生成“基于政策選擇組合”的客戶定制代碼,也可用來避免生成對某些特殊類型并不適合的代碼。
- 由于template metaprograms執行于C++編譯期,因此可將工作從運行期轉移到編譯期。這導致的一個結果是,某些錯誤原本通常在運行期才能偵測到,現在可在編譯期找出來。另一一個結果是,使用TMP的C++程序可能在每一方面都更高效:較小的可執行文件、較短的運行期、較少的內存需求。然而將工作從運行期移轉至編譯期的另一個結果是,編譯時間變長了。
- TMP中if…else…可通過模板函數重載實現;TMP循環結構可使用遞歸;TMP遞歸涉及遞歸模板具現化,如下圖計算階乘: