国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

Pure C++:泛型編程:模板特殊化

2019-11-17 05:49:06
字體:
來源:轉載
供稿:網友
    在上一期專欄中我已經談到過,執行的操作不僅包括簡單存儲和檢索操作的參數化類型僅限于可安全綁定到它的可接受類型 [請參閱 Pure C++: CLR Generics Versus C++ Templates(英文)]。使用泛型,可以通過 where 子句顯式加上這些限制。
在 C++/CLI 模板工具中,通過將函數模板或類模板(單個成員函數或整個類)非凡化,通常可以避免這些限制。例如,將 min 函數添加到上一期專欄的 tStack 類中。通常,我會使用常規 min 算法,但那種算法僅在我作為程序員的時候有用,而對我撰寫有關模板非凡化的文章沒有幫助。為了方便起見,圖 1 中重現了 tStack 類的模板定義。    圖 2 顯示了 min 的一種可能的實現方法。我將定義一個局部變量 min_val 來存放最小元素,并將它初始化為容器的第一個元素。然后定義兩個迭代程序,將每個元素與 min_val 進行比較,假如其值比 min_val 小則為 min_val 重新賦值。現在,您能看出隱含的限制嗎?假如能,則您會得到: if ( *it < min_val )    通常,對于 min 函數,只有能夠使用內置小于 (<) 運算符的類型或本身具有 Operator<() 實例的類型才能綁定到 elemType 的類型。假如某個類型沒有定義 operator<(),并嘗試對此類型的項的 tStack 調用 min,則在 min 中使用無效的比較運算符時將出現編譯時錯誤。例如,System::String 類沒有小于 (<) 運算符(它使用 IComparable 的 CompareTo 方法)。因此,假如我嘗試對使用 String 實例化的 tStack 調用 min,則它在編譯時就會出錯,因為該比較操作失敗了。    有一種解決方案我不會使用:定義全局運算符 operator<(),該運算符使用 CompareTo 來比較兩個 String 類型的值。然后,tStack<String^>::min() 會自動調用這些全局運算符: bool operator<( String^ s1, String^ s2 ) {return ( s1->CompareTo( s2 ) < 0 ) ? true :false;}    請記住,目標是防止當用戶指定的類型參數為 String 時實例化 tStack::min 成員函數定義,而希望使用 CompareTo 方法來定義自己的 tStack<String^>::min 實例。您可以使用顯式模板非凡化定義為類模板實例化的成員提供非凡化的定義,來實現此目的。此定義指明了模板名稱、指定模板的參數、函數參數列表和函數主體。要害字模板的后面是小于 (<) 和大于 (>) 標記,然后是類成員非凡化的定義(請參閱圖 3)。    即使類的類型 tStack<String^> 是從常規類模板定義(即由編譯器內部生成的專用于 String 的實例,其中每個 elemType 占位符都被替換為 String 類型)實例化的,類型 tStack<String^> 的每個對象都會調用非凡化的成員函數 min。tStack::min 成員函數定義既不會被擴展,也不會在 tStack<String^> 中使用。    在有些情況下,可能整個類模板定義都不適合某種類型。在這種情況下,程序員可以提供一種定義來非凡化整個類模板。程序員可以提供 tStack<String^> 的定義: template <class elemType>ref class tStack;// 類模板非凡化template<> ref class tStack<String^> {public:tStack();String^ pop();void push( Stack^ et ); // ...};    只有在聲明了常規類模板后,才能定義顯式類模板非凡化。假如您提供完整的類模板非凡化,則必須定義與此非凡化關聯的每個成員函數或靜態數據成員。類模板的常規成員定義決不能用于創建顯式非凡化的成員定義,也不會被交叉檢查。這是因為類模板非凡化的類成員集可能與常規模板的類成員集完全不同。    定義完全非凡化的類模板(如 tStack<String^>)的成員時,請勿在其定義前添加非凡的 template<> 標記,而應該通過顯式列出實際的類型來指明非凡化定義,如下所示: // 定義類模板非凡化的// 成員函數 min()String^ tStack<String^>::min() { ... }
局部模板非凡化
    假如類模板有多個模板參數,您可以針對一個或一組特定的參數化值或類型來非凡化類模板。也就是說,您可能希望提供一個模板,使其除了某些模板參數已被實際類型或實際值替換以外,其他均與常規模板匹配。使用局部模板非凡化就可以實現此目的。例如,假設存在下面的 Buffer 類模板: template <class elemType, int size>ref class Buffer { ... };下面說明如何對 Buffer 使用局部非凡化,使其能夠很好地處理大小為 1KB 的緩沖區: // 類模板 Buffer 的局部非凡化template <class elemType>ref class Buffer<elemType,1024> {// 對 1KB 大小使用非凡算法...};    Buffer 的局部非凡化只有一個類型參數 elemType,因為大小的值固定為 1024。局部模板非凡化的參數列表只列出了模板參數仍然未知的參數。但是,當您定義該模板的實例時,必須同時指定這兩個參數(這與對一個參數使用默認值的情形不同)。在下面的示例中,局部類模板非凡化是用 elemType 為 String 的類型參數實例化的: Buffer<String^,1024> mumble;但是,假如您改為下面的代碼行,則編譯器會生成錯誤,并將聲明標記為缺少第二個參數: Buffer<String^> mumble; // 錯誤為什么會這樣呢?假如開發人員以后引入一組非凡化的 Buffer(如下所示),會出現什么情況? template <class elemType>ref class Buffer<elemType,4096> {};template <class elemType> ref class Buffer<elemType,512> {};假如前面示例的聲明中不要求使用第二個參數,編譯器就無法區分這幾種非凡化!    局部非凡化與其對應的完整常規模板同名,在本例中為 Buffer。這就帶來了一個有趣的問題。請注重,Buffer<String^,1024> 的實例化既可以通過類模板定義進行,也可以通過局部非凡化進行。那么,為什么會選擇局部非凡化來實例化該模板呢?一般的規則是:假如聲明了局部類模板非凡化,編譯器就會選擇最非凡化的模板定義進行實例化。只有在無法使用局部非凡化時,才會使用常規模板定義。    例如,當必須實例化 Buffer<String^,2048> 時,由于此實例化與任何一個局部模板非凡化都不匹配,因此會選擇常規模板定義。    局部非凡化的定義完全不同于常規模板的定義。局部非凡化可以擁有一組與常規類模板完全不同的成員。局部類模板非凡化的成員函數、靜態數據成員和嵌套類型必須有自己的定義,這與類模板非凡化相同。類模板成員的常規定義決不能用于實例化局部類模板非凡化的成員。    類模板的局部模板非凡化構成了現代 C++ 用法中一些非常復雜的設計慣用語的基礎。假如您對此感愛好,可以閱讀 Andrei Alexandrescu 撰寫的《Modern C++ Design: Generic PRogramming and Design Patterns Applied》(Addison-Wesley,2001 年版),了解此用法的具體信息。
進入討論組討論。
函數模板非凡化    非成員函數模板也可以進行非凡化。在有些情況下,您可以充分利用有關類型的一些專門知識,來編寫比從模板實例化的函數更高效的函數。在其他一些情況下,常規模板的定義對某種類型而言根本就是錯誤的。
例如,假設您擁有函數模板 max 的定義: template <class T>T max( T t1, T t2 ) {return ( t1 > t2 ? t1 :t2 );}    假如用 System::String 類型的模板參數實例化該函數模板,所生成的實例就無法編譯,因為正如您在前面所看到的,String 類不支持小于 (<) 或大于 (>) 運算符。圖 4 中的代碼說明了如何非凡化函數模板。(同樣必須先聲明常規函數模板才能進行非凡化。)    假如可以從函數參數推斷出模板參數,則可以從顯式非凡化聲明中對實際類型參數省略函數模板名稱的限定 max<String^>。例如,編譯器可以在下面的 max 模板非凡化中推斷出 T 綁定到 String,因此在這種情況下,為方便起見,該語言答應使用下面的簡寫表示法: // 沒問題:從參數類型推斷出 T 綁定到 Stringtemplate<> String^ max( String^, String^ );引入此顯式非凡化后,下面的調用就會解析為這個非凡化的實例: void foo( String^ s1, String^ s2 ) {String^ maxString = max( s1, s2 ); // ...}
假如兩個參數的類型均為 String,常規函數模板不會擴展。這正是我們所需要的行為。只要提供了顯式函數模板非凡化,就必須始終指定 template<> 和函數參數列表。例如,max 的下面兩個聲明不合法,并且在編譯時會被標記為: // 錯誤:無效的非凡化聲明// 缺少 template<>String^ max<String^>( String^, String^ );// 缺少函數參數列表template<> String^ max<String^>;    有一種情況,省略函數模板非凡化的 template<> 部分不是錯誤。即,在您聲明的普通函數帶有與模板實例化相匹配的返回類型和參數列表的情況下: // 常規模板定義template <class T>T max( T t1, T t2 ) { /* ... */ }// 沒問題:普通函數聲明!String^ max( String^, String^ );    毫無疑問,您經常會感到很無奈,并認為 C++ 真是太難理解了。您可能想知道,究竟為什么所有人都希望聲明與模板實例化相匹配的普通函數,而不希望聲明顯式非凡化。那么,請看下面的示例,事情并不是完全按照您喜歡的方式進行的: void foo( String^ s1, String^ s2 ) {// 能否解析非凡化的實例?String^ maxString = max( "muffy", s2 ); // ... }在 C++/CLI 下,對于重載解決方案,字符串文字的類型既是 const char[n] [其中 n 是文字的長度加一(用于終止空字符)],又是 System::String。這意味著,給定一組函數 void f( System::String^ ); // (1)void f( const char* ); // (2)void f( std::string ); // (3)如下所示的調用 // 在 C++/CLI 下解析為 (1)f( "bud, not buddy" );    與 (1) 完全匹配,而在 ISO-C++ 下,解析結果會是 (2)。因此,問題就是,對于函數模板的類型推斷而言,字符串文字是否還是被當作 System::String 進行處理?簡言之,答案是“不”。(具體的答案將是我下一期專欄的主題,該專欄將具體介紹函數模板。)因此,不選擇 max 的非凡化 String 實例,下面對 max 的調用 String^ maxString = max( "muffy", s2 ); // 錯誤在編譯時會失敗,因為 max 的定義要求兩個參數的類型均為 T: template <class T> T max( T t1, T t2 );那您能做些什么呢?像在下面的重新聲明中一樣,將模板改為帶有兩個參數的實例 template <class T1,class T2> ??? max( T1 t1, T2 t2 );    使我們能夠編譯帶有 muffy 和 s2 的 max 的調用,但會因大于 (>) 運算符而斷開;并且指定要返回的參數類型。     我想做的就是始終將字符串文字強制轉換為 String 類型,這也是拯救普通函數的方法。    假如在推斷模板參數時使用了某個參數,那么只有一組有限的類型轉換可用于將函數模板實例化的參數轉換為相應的函數參數類型。還有一種情況是顯式非凡化函數模板。正如您所看到的,從字符串文字到 System::String 的轉換不屬于上述情況。    在存在有害字符串文字的情況下,顯式非凡化無助于避免對類型轉換的限制。假如您希望不僅答應使用一組有限的類型轉換,則必須定義普通函數而不是函數模板非凡化。這就是 C++ 答應重載非模板函數和模板函數的原因。進入討論組討論。
    我基本上已經講完了,不過還有最后一點需要說明。創建一組您在圖 5 中看到的 max 函數意味著什么?您知道調用 max( 10, 20 );始終會解析為常規模板定義,并將 T 推斷為 int。同樣,您現在還知道調用
max( "muffy", s2 );max( s2, "muffy" );始終會解析為普通函數實例(其中文字字符串轉換為 System::String),但是有一個問題,調用 max( s2, s2 );會解析為三個 max 函數中的哪一個?要回答此問題,我們要查看解析重載函數的過程。
重載函數的解析過程
    解析重載函數的第一步是建立候選函數集。候選函數集包含與被調用的函數同名并且在調用時能夠看到其聲明的函數。    第一個可見函數是非模板實例。我將該函數添加到候選列表中。那么函數模板呢?在能夠看到函數模板時,假如使用函數調用參數可以實例化函數,則該模板的實例化被視為候選函數。在我的示例中,函數參數為 s2,其類型為 String。模板參數推斷將 String 綁定到 T,因此模板實例化 max(String^,String^) 將添加到候選函數集中。    只有在模板參數推斷成功時,函數模板實例化才會進入候選函數集。但是,假如模板參數推斷失敗,不會出現錯誤;即,函數實例化沒有添加到候選函數集中。    假如模板參數推斷成功,但是模板是為推斷出的模板參數顯式非凡化的(正如我的示例一樣),會怎么樣呢?結果是,顯式模板非凡化(而不是通過常規模板定義實例化的函數)將進入候選函數。因此,此調用有兩個候選函數:非凡化的模板實例化和非模板實例。 // 候選函數// 非凡化的模板...template<> String^ max<String^>( String^ s1, String^ s2 );// 非模板實例String^ max( String^, String^ );    解析重載函數的下一步是從候選函數集中選擇可行函數集。對于要限定為可行函數的候選函數,必須存在類型轉換,將每個實際參數類型轉換為相應的形式參數類型。在該示例中,兩個候選函數都是可行的。    解析重載函數的最后一步是,對參數所應用的類型轉換進行分級,以選擇最好的可行函數。例如,兩個函數看起來都很好。既然兩個函數都可行,那么這是否應該被視為不明確的調用?    實際上,調用是明確的:將調用非模板 max,因為它優先于模板實例化。原因是,在某種程度上,顯式實現的函數比通過常規模板創建的實例更為實用。    令人吃驚的是,在解決有害字符串文字的情況中,我已經徹底消除了調用以前的 String 非凡化的可能性,因此我可以消除這個問題。我只需要常規模板聲明以及重載的非模板實例: // 支持 String 的最終重載集template <class T>T max( T t1, T t2 ) { /* ... */ }String^ max( String^, String^ );    這不一定會很復雜,但有一點是肯定的 - 在語言集成和靈活性方面,它遠遠地超過了公共語言運行時 (CLR) 泛型功能可以支持的范圍。     模板非凡化是 C++ 模板設計的基礎。它提供了最好的性能,克服了對單個或系列類類型的限制,具有靈活的設計模式,并且在實際代碼中已證實其巨大價值。在下一期專欄中,我將深入分析 C++/CLI 對模板函數和常規函數的支持。
請將您的疑問和意見通過 purecpp@microsoft.com 發送給 Stanley。Stanley B. Lippman 是 Microsoft 公司 Visual C++ 團隊的體系結構設計師。他從 1984 年開始在 Bell 實驗室與 C++ 的設計者 Bjarne Stroustrup 一起研究 C++。此后,他在 Disney 和 DreamWorks 制作過動畫,還擔任過 JPL 的高級顧問和 Fantasia 2000 的軟件技術主管。轉到原英文頁面進入討論組討論。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 读书| 蒲城县| 西贡区| 望城县| 盈江县| 繁峙县| 拉萨市| 呼伦贝尔市| 武山县| 麻城市| 旺苍县| 新晃| 康保县| 平谷区| 正阳县| 阿鲁科尔沁旗| 上高县| 赣州市| 天台县| 改则县| 巴彦淖尔市| 阳高县| 乌兰浩特市| 双鸭山市| 根河市| 柘荣县| 兴化市| 呼玛县| 石楼县| 广丰县| 克什克腾旗| 新建县| 阜平县| 凌云县| 湖北省| 宜君县| 体育| 拉孜县| 体育| 沐川县| 泸溪县|