之前學習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 T1, class T2> // 普通版本,有兩個模板參數
class B { ..... };
template<class T2> // 偏特化版本,指定其中一個參數,即指定了部分類型
class B<int , T2> { ..... }; // 當實例化時的第一個參數為int 則會優先調用這個版本
第二種形式,也是最重要的版本
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)處理方式