C++ 模板偏特化-来自STL的思考
之前學習STL時接觸過一段時間的模板,模板是C++泛型編程編程的基礎
STL從頭到尾都是模板泛型編程,我覺得用的最巧妙的就是在traits萃取技巧時用到的模板偏特化
- 先簡要回顧一下模板吧,模板主要分為函數模板與類模板
函數模板
template<class T> T add(T a, T b) { return a + b;}int main() {int a = 1, b = 2;std::cout << add(a + b) << std::endl;return 0; }如上就是最簡單的函數模板,當實例化函數模板時,編譯器會自動進行實參類型推導
上面類型T就被自動推導為int類型
類模板
templete<class T> class A { public:explicit A(T val) : t(val) { }T add(T x) { return t + y; } privete:T t; };int main() {A<int> a(10);std::cout << a.add(5) << std::endl;return 0; }如上就是最簡單的類模板,實例化類模板必須要指定類型,編譯器無法為類模板自動推導類型
幾個需要注意的點
1.類模板的和函數模板都必須定義在.h頭文件中
2.模板的實例化類型確定是在編譯期間
3.只是模板寫好了,編譯一般不會很多出錯,出錯一般會在實例化編譯之后
4.模板實例化只會實例化用到的部分,沒有用到的部分將不會被實例化
- 我覺得模板的特例化是模板中比較精髓的東西
有函數模板特例化,類模板特例化,其中又分為全特化與偏特化
主要的用途都是對于特定的類型,指定特定的處理方式
就相當于普通編程中if-else if - else這樣的方式
編譯階段確定如果是某個特化類型,就用特化的模板
如果都不是,就用最一般的模板
函數模板特例化
函數模板只能全特化,不能偏特化,如果要偏特化的話只能重載
函數模板全特化
template< > // 全特化 注意語法 double add(double a, double b) { return a + b; }int main() {int x = 10, y = 20;double z = 1.1, w = 2.2;std::cout << add(x, y) << std::endl; // 調用普通版本std::cout << add(z, w) << std::endl; // 調用全特化版本return 0; }如果有與實參更加匹配的特例化版本,編譯器將會選擇特例化版本
函數模板重載(不存在偏特化)
因為偏特化版本本質上仍然是模板,所以如果需要的話,可以重載一個函數模板
template<class T1> // 重載版本,接收參數為指針 T1 add(T1* a, T1* b) { return *a + *b; } int main() {int a = 10, b = 20;int *x = &a, *y = &b;add(a, b); // 調用普通模板add(x, y); // 調用重載的模板return 0; }如上,如果需要一個接收指針的偏特化版本,那么可以用重載模板實現
函數模板不存在偏特化
類模板特例化
類模板既有全特化,又有偏特化
這里重新寫一個更一般的模板類來說明類模板的特例化
類模板全特化
類模板全特化比較好理解,跟函數模板一樣,全特化是一個實例,當編譯器匹配時會優先匹配參數一致的實例
template< > // 注意語法 class A<char*> // 一個全特化的模板類A { // 當用char*類型來實例化類模板A時,將會優先調用這個全特化實例 public:explicit A(char* val) : t(val) { }char* add(char* a, char* b) { return strcat(a, b); } private:char* t; };類模板的偏特化
類模板的偏特化會稍微復雜一點點,它有多種形式
類模板偏特化本質上都是指定部分類型,讓偏特化版本稱為普通版本的子集,若實例化時參數類型為指定的類型,則優先調用特例化版本
第一種形式
第二種形式,也是最重要的版本
template<class T> // 普通版本 class B { ..... };template<class T> //這個偏特化版本只接收指針類型的模板實參 class B<T*> { ..... }; template<class T> class B<T&> { ..... }; // 這個偏特化版本只接受引用類型的模板實參第三種形式
template<class T> //普通版本 class B { ..... };template<class T> // 這種只接受用T實例化的vector的模板實參.也是一種偏特化 class B<vector<T>> { ...... };幾個值得注意的地方
1.特例化本質上是我們頂替了編譯器的工作,我們幫編譯器做了類型推導
2.全特化本質上是一個實例,而偏特化本質上還是一個模板,只是原來模板的一個子集
3.所以全特化的函數模板,本質上是實例,從而不會與函數模板產生二義性
4.若想讓用戶能使用特例化版本,特例化版本必須與模板定義在同一個.h頭文件中
- STL中的迭代器實現與高效實現與模板偏特化息息相關.
類模板偏特化與STL
偏特化在STL中最重要的兩個應用
1.應用在迭代器設計中,為了使迭代器既可以萃取出值類型,又可以包容原生指針
如果要通過一個迭代器就能知道它的值類型,那么一般會使用iterator_traits
迭代器萃取技術的兩個核心是:
1)在每個迭代器類中定義value_type值類型的類型成員,這樣直接通過迭代器的value_type類型成員就可以知道值類型
2)問題就在于,迭代器必須兼容原生指針,而原生指針很難被重新定義,即要在原生指針的類中添加value_type的值類型的類型成員.這時候,靠的就是類模板的偏特化了.新添加一層iterator_traits類,專門萃取迭代器的屬性,然后再對iterator_traits類設計原生指針與原生引用的偏特化版本,就解決了這個棘手的問題
2.type_traits類型萃取,對待特殊類型,特殊處理,提高效率
對于沒有構造函數,析構函數等的內置類型,如果與復雜類型一樣,執行同樣的操作,顯然是效率不高的
先實現一個對所有類型都設置一個最保守值的type_traits模板類,然后再對每個內置類型設置偏特化版本,內置類型設置一個更為激進的值,表明可以采取更為高效的操作來提高效率
比如copy函數,如果傳遞的對象是一個復雜類型,那么可能只能采取最保守的處理方式,一個一個的構造;如果是內置類型,這樣顯然太低效,使用memcpy()可能會好一些
其實iterator_traits也不止是處理兼容原生指針的問題,它也可以提高效率.
迭代器分為很多種,有可以隨機訪問的(vector),有只能前后一個一個移動的(list),也有只能單向移動的(slist),所以一般把迭代器分為五種:
InputIterator 輸入迭代器
OutputIterator 輸出迭代器
ForwardIterator 單向迭代器
BidirectionIterator 雙向迭代器
RandomAccessIterator 隨機訪問迭代器
比如一個advance(n)函數,對于單向迭代器只能一個一個移動過去,但是這種實現對于隨機訪問迭代器顯然不是理想的處理方式
處理的方式就是先實現這五個類,用作標記用,在每個迭代器里面都定義迭代器類型的類型成員iterator_catagory,再對不同版本的迭代器實現不同的advance(n)處理方式
轉載于:https://www.cnblogs.com/yyehl/p/7253254.html
總結
以上是生活随笔為你收集整理的C++ 模板偏特化-来自STL的思考的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 函数的闭包和装饰器
- 下一篇: c#中Class和Struct使用与性能