條款41:了解隱式接口和編譯期多態
? ? ? ? 面向對象編程世界總是以顯式接口(源碼可見的接口)和運行期多態(virtual)解決問題;對于templates及泛型編程的世界,隱式接口和編譯期多態顯得更加重要;
? ? ? ? 對于template,只有當參數具體確定時(具現化)才能確定具體操作,而這些具現化的行為發生在編譯期,以不同的template參數具現化function templates會導致調用不同的函數,這就是所謂的編譯期多態。
? ? ? ? ?通常顯示接口由函數的簽名式(函數名稱,參數類型,返回類型)構成,而隱式接口則不基于函數簽名式,它是由有效表達式組成,表達式自身看起來很復雜,但它們要求的約束條件一般而言直接而又明確;
? ? ? ? template參數身上的隱式接口,和class對象上的顯示接口,都是在編譯器完成檢查;
? ? ? ? 對template參數而言,接口是隱式的,基于有效表達式;多態則是通過template具現化和函數重載解析發生于編譯器;
條款42:了解typename的雙重意義
? ? ? ? ? 在聲明template類型參數時,class和typename意義完全相同;
? ? ? ? ? 引申:template內出現的名稱如果相依于某個template參數,稱之為從屬名稱,如果從屬名稱在class內呈嵌套狀,我們稱它為嵌套從屬名稱,如c::const_iterator就是這樣的名稱,它其實是一個嵌套從屬類型名稱,也就是個嵌套從屬名稱并指涉某類型;如果一個名稱(int)并不依賴于template參數的名稱,這樣的名稱稱為謂非從屬名稱;代碼如下:
template<typename c>
void print2nd(const c&container)
{
? ? ? ? ? ?if(container.size()>=2){
? ? ? ? ? ? ?c::const_iterator iter(container.begin( ));
? ? ? ? ? ? ? ++iter;
? ? ? ? ? ? ? int value=*iter;
? ? ? ? ? ?}
}
? ? ? ? 嵌套從屬名稱會導致編譯困難,如c::const_iterator *p;C++解析代碼有一個規定:如果解析器在template中遭遇一個嵌套從屬名稱,它便假設這個名稱不是個類型,除非你告訴它是,所以缺省情況下,嵌套從屬名稱不是類型(有一個例外);正確的情況就是告訴C++解析器這個一個類型,在名稱前加關鍵字typename就可以了;
? ? ? ?typename只被用來驗明嵌套從屬類型名稱,其他名稱不該有它的存在;typename作為嵌套從屬類型名稱的前綴詞有一個例外:typename不可以出現在base classes list內的嵌套從屬結構,也不可在member initialization list(成員初始列表)中作為base class修飾符,如:
template <typename T>
class derived:public base<T>::Nested{
public:
? ? ? ? explicit derived(int x):base<T>::Nested(x){
? ? ? ? ? typename base<T>::Nested temp;
? ? ? ? }
};
條款43:學習處理模塊化基類內的名稱
? ? ? ? ? ? ? 考慮如下的程序代碼:
template <typename company>? ? ? ? ? ? ? ? ? ? ? ? ? ?template<typename company>
class msgsender{? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? class loggingmsgsender:public msgsender<company>{
public:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? public:
? ? ? ?void sendclear(const msginfo & info)? ? ? ? ? ? ? ? void sendclearmsg(const msginfo & info){
? ? ? {
? ? ? ? ? std::string msg;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? sendclear(info);//調用base class函數,代碼編譯不通過
? ? ? ? ?company c;
? ? ? ? c.sendcleartext(msg);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?}
? ? }? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? };
};
? ? ? 分析:當編譯器遇到class template loggingmsgsender定義式,并不知道它繼承什么樣的class,當然它繼承的是msgsender<company>,但其中的company是個template參數,不到后來(loggingmsgsender被具現化)無法確切知道它是什么,也就不知道是否有sendclear這個函數;C++編譯器拒絕調用sendclear的原因:它知道base classtemplates有可能被特化,而那個特化的版本不提供和一般性template相同的接口;
? ? ? 解決C++不進入templatized base classes的方法有三種:1)在base class函數調用之前加上this->,也就是this->sendclear(info);2)使用using聲明式,告訴編譯器sendclear在base class內,如using msgsender<company>::sendclear;3)明確指出被調用函數位于base class內,如msgsender<company>::sendclear(info);但是如果調用的是virtual函數,那么就會關閉virtual綁定行為;
條款44:將與參數無關的代碼抽離templates
? ? ? ? ?templates是節省時間和避免代碼重復的一個奇方妙法,但是使用templates可能會導致代碼膨脹,其二進制碼帶著重復的代碼數據;可以通過使用共性和變性分析的方法解決這個問題,將多個class的共同部分搬離到新的class,并通過繼承或復合的方式來得到公共屬性;
? ? ? ? ?任何templates代碼都不該與某個造成膨脹的templates參數產生相依的關系;
? ? ? ? ?因非類型模板參數而造成的代碼膨脹,往往可消除,做法是以函數參數或class成員變量替換templates參數,如矩陣類求逆實現;
? ? ? ? ?因類型參數而造成的代碼膨脹,往往可以降低,做法是讓帶有完全相同二進制表述的具現類型共享實現代碼,如int和long實現;
? ? ? ?如果你實現某些成員函數而他們操作強型指針(如T*),你應該令他們調用另一個操作無類型指針(void *)的函數,后者完成實際工作;
條款45:運用成員函數模板接受所有兼容類型
? ? ? ? ? ?真實指針支持隱式轉換,如derived class隱式轉換成base class;但是同一個templates的不同具現體之間并不存在什么與生俱來的固有關系(如某個帶有base-derived關系的B,D兩類型分別具現某個template,產生的兩個具現體并不帶有base-derived關系);
? ? ? ? ? ?C++給我們提供一個稱為member function templates,它為模板類提供構造函數;這一類構造函數通過對象u創建對象t,而u和t的類型是同一個template的不同具現體,我們稱這個函數為泛化構造函數,如:
template<typename T>
class smartptr{
public:
? ? ? ?template<typename u>
? ? ? smartptr(const smartptr<u>& other);
};
? ? ? ? ?但是對于泛化構造函數,有時我們需要指定隱式轉換的方向,例如我們不能將int *轉換為double*,這時候我們可能需要一個指針指向class的原始數據,這樣問題就變成只有存在兩個底層指針可以轉換的才是我們需要的結果;
? ? ? ? member function templates(成員函數模板)的效果不限于構造函數,它還支持賦值操作,如shared_ptr類的代碼:
template<typename T>
class shared_ptr{
public:
? ? template<class Y>
? ? ?explicit shared_ptr(Y* p);//沒有const,原因???
? ? ?template<class Y>
? ? ?shared_ptr(shared_ptr<Y>const&r);//沒有explicit,允許隱式轉換
? ? ?template<class Y>
? ? ?explicit shared_ptr(weak_ptr<Y>const&r);
? ? template<class Y>
? ? ?explicit shared_ptr(auto_ptr<Y>&r);//沒有const,auto_ptr性質決定
? ? template<class Y>
? ? ?shared_ptr&operator=(shared_ptr<Y>const &r);
? ? template<class Y>
? ? ?shared_ptr&operator=(auto_ptr<Y>&r);//沒有const,auto_ptr性質決定
};
? ? ?member function templates會聲明一個泛化的copy構造函數和copy assignment操作符,但是它不會改變C++語言的規則,C++語言規定如果程序需要一個copy構造函數,你沒有聲明,編譯器會自動幫你生成一個,相同規則也適用于賦值操作,因此如果你不想讓系統聲明正常的拷貝構造函數和賦值操作符,你得自己聲明;
條款46:需要類型轉換時請為模板定義非成員函數
? ? ? ? ?當不存在模板時,如果函數參數都存在隱式轉換,則需要將這個函數定義成非成員函數;當存在模板時,這條規則又發生了變化,編譯無法通過,因為在template具現化實參推導過程中從不將隱式類型轉換函數納入考慮;
? ? ? ? 解決的方法:利用friend關鍵字,將非成員函數聲明為友元函數(新用法),同時提供實現(實現在函數外,通過友元函數調用,這樣可以實現代碼沖擊最小化)(如果只是單純提供友元聲明,那么可以通過編譯,卻沒有辦法完成連接),這種稱為混合式代碼調用,代碼調用成功的原因是:函數在被調用的過程中參數可以實現隱式變換;
? ? ? ?類模板的聲明式為:template<typename T> class rational;? ? ?