特化與重載,特化重載
現在我們已經知道如何使一個泛型定義擴展成一些相關的類家族和函數家族。但是有些時候,我們需要同一個名字的函數的不同實現(
透明定義),為的是在不同情況下獲得較高的性能,在這種情況下就不是簡單的參數替換就能夠解決的。
一、透明定義使用的技術
對於函數模板使用的透明定義的技術是:
函數模板的重載
對於類模板的使用透明定義的技術是:
類模板的特化(全局特化和局部特化)
二、重載函數模板
兩個名稱相同的模板可以同時存在,還可以對他們進行實例化。
template<typename T>
int f(T)
{
retrun 1;
}
template<typename T> //重載版本
int f(T*)
{
return 2;
}
如果我們用int*來替換第一個模板參數T,用int來替換第二個模板參數T,納悶將會獲得兩個具有相同參數類型和返回類型的同名函數。
int main()
{
cout << f<int*>((int*)0) << endl; //這裡回實例化出兩個實體,根據實參(int*)選擇接近的
cout << f<int>((int*)0) << endl; //這裡會實例化出兩個實體,根據實參(int*)選擇接近的
}
注意:表達式0是一個整數,而不是一個null指針常量。只有在發生特定的隱式轉型之後參會成為一個null的指針常量,但是在模板實參演繹的過程中並不會考慮這種轉型。
三、簽名
只要具有不同的簽名,就可以在同一個程序中存在,注意是
同一個程序而不是同一個翻譯單元。
對函數簽名的定義如下:
(1)非受限的函數名稱
(2)函數名臣各所屬的額類的作用域獲名字空間作用域,如果函數名稱具有內部連接屬性還包括名稱聲明所在的翻譯單元
(3)函數const,volatile或者是cons volatile限定符
(4)函數的參數類型(如果是產生自模板的,指的是模板參數被替換之前的類型)
(5)如果這個函數產生自函數模板,那麼還包括它的返回值類型
(6)如果這個函數是產生自函數模板,那麼包括模板參數,模板實參。
template<typename T1, typename T2>
void f1(T1, T2);
template<typename T1, typename T2>
void f2(T2, T1);
template<typename T>
long f2(T);
template<typename T>
char f2(T);
如果上面的這些模板是在同一個作用域中進行聲明的話,我們不能使用某些模板,因為實例化過程會導致
二義性
#include<iostream>
template<typename T1, typename T2>
void f1(T1, T2)
{
std::cout << "f1(T1, T2)\n";
}
template<typename T1, typename T2>
void f1(T2, T1)
{
std::cout << "f1(T2, T1)\n";
}
int main()
{
f1<char, char>('a', 'b'); //錯誤:二義性
}
因此只有在這兩個模板出現不同的翻譯單元,它們兩個實例化體才可以在同個程序同時存在。
//翻譯單元1
#include <iostream>
template<typename T1, typename T2>
void f1(T1, T2)
{
std::cout << "f1(T1, T2)\n" ;
}
void g()
{
f<char, char>('a', 'b');
}
//翻譯單元2
#include<iostream>
template<typename T1, typename T2>
void f1(T2, T1)
{
std::cout << "f1(T2, T1)\n";
}
extern void g(); //定義在單元1
int main()
{
f<cahr, char>('a', 'b');
g();
}
最後輸出:
f1(T2, T1);
f1(T1, T2);
四、顯示特化類模板
C++標准的“顯示特化”概念指的是一種語言特性,我們通常稱之為全局特化。
它為類模板提供了一種使模板參數可以被全局替換的實現,而沒有剩下的模板參數。事實上,類模板,函數模板都可以被全局特化,而類模板的成員(成員函數, 嵌入類, 靜態成員變量等,他們的定義可以位於類定義的外部)也可以被全局特化。
(一)如何顯示特化
template<typename T>
class S{
public:
void f(){
std::cout << "generic(S<T>::info())" << std::endl;
}
};
template<> //顯示特化
class S<void>{
public:
void msg(){ //新增加了msg函數
std::cout << "fully specialized (S<void>::msg())" << std::endl;
}
}
實際上,全局特化之和類模板的名稱關聯,可以包含名稱不同的成員函數。
(二)模板特化聲明
(模板)全局特化的
聲明不一定是
定義。當一個全局特化
聲明之後,針對該特化的模板實參列表調用,將不再使用模板的泛型
定義,而是使用這個特化的
定義。所以如果在一個調用處需要該特化的
定義,而在這之前沒有提供
定義(光有
聲明)那麼程序將會出現錯誤。
template<typename T>
class Types{
public:
typedef int I;
};
template<typename T, typename U = typename Types<T>::I>//注意這裡有默認形參
class S; //(1)
template<>
class S<void>{ //(2)
public:
void f();
};
template<>class S<char, char>; //(3)
template<> class S<char, 0>; //錯誤應該是一個類型
int main()
{
S<int>* p1; //使用(1)處聲明, 不需要定義
S<int> e1; //使用(1)處聲明, 需要定義,但是找不到定義
S<void> pv; //使用(2)處聲明
S<void, int> sv;//使用(2)處聲明, 使用默認形參
S<void, char> e2; //使用(1)處聲明, 需要找到定義,但是找不到定義
S<char, cahr> e3; //使用(3)處聲明,需要定義,但是找不到定義
}
template<>
class S<char, char>{}; //(3)處的定義
(三)如何定義全局模板特化成員
對於特化聲明而言,因為它並不是模板聲明,所以應該使用普通的成員定義語法,來定義全局類模板特化的成員(即不能使template<>前綴)
template<typename T>
class S;
template<> class S<char**> {
public:
void print() const;
}
void S<char**>::print() const //這裡不能使用template<>
{
std::cout << "使用普通成員定義語法來定義全局類模板特化成員函數" << std::endl;
}
(四)全局特化版本與實例化版本不能共同存在一個程序中(即使是在不同的翻譯單元)。
template<typename T>
class Invalid{};
Invalid<double> x1; //此處將產生一個Invalid<double>實例化體
template<>
class Invalid<double>; //錯誤:Invalid<double>已經被實例化了
(五)特化全局成員函數
template<typename T> //(1)泛型模板定義
class Outer{
public:
template<typename U>
class Inner{
private:
static int count;
};
static int code; //(4)
coid print() const{ //(5)
std::cout << "generic" << std::endl;
}
};
template<tyepname T>
int Outer<T>::code = 6; //泛型模板靜態成員定義
template<typename T> template<typename U>
int Outer<T>::Inner<U>::count = 7;// 泛型模板靜態成員定義
template<>
int Outer<void>::count = 12; //特化全局成員函數
template<>
int Outer<void>::print() const
{
std::cout << “Outer<void>” << std::endl;
}
定義將會代替類Outer<void>(4)和(5)的泛型定義, 類Outer<void>的其他成員仍然默認的按照(1)處的泛型模板定義產生。
另外由於這樣也算全局特化了Outer<void>,所以不能再次提供Outer<void>的顯式特化。
(六)template<>不能位於template<類型>後面,也就是說,不能先特化Inner,而不特化Outer。
五、局部的類模板特化
因為該模板定義所使用的模板實參只是被局部的指定所以被稱為局部特化。
(一)怎樣個局部指定法?
template<int N1, int N2>
class S{}; //(1)
template<2, 3> //全局特化
class S{};
template<int N> //(2)局部特化,改模板的實參只是被局部指定。
class S<2, N>{};
這樣就把類模板特化成一個“針對模板實參”的類家族,上面的例子就是針對第一個模板實參為2的類家族。
(1)處叫做基本模板。(2)處叫做局部特化
-------------------------------------------------------------
template<typename T>
class List{
public:
void append(T const&);
inline size_t length() const;
};
template<> //全局特化
class List<void*>{
public:
void append(void* const&);
inline size_t length() const;
};
template<typename T> //局部特化
class List<T*>{
private:
List<void*> impl;
public:
void append(T* p){
impl.append(p);
}
size_t length() const{
return impl.length();
}
};
上面的例子就是針對模板實參為指針的類家族,並且這裡使用了一點小技巧:
該特化使用了“委托代理”,將所有該類家族的處理交由List<void*>實例處理。這樣紙就解決了C++模板備受指責的代碼膨脹問題。
為此上面的例子還要一個List<void*>的全局特化,以便編譯器可以找到該實例。
template<>
class List<void*>{
void append(void* p);
inline size_t length() const;
};
針對局部特化有以下的一些約束條件:
(1)局部特化的參數必須和基本模板參數在種類上相匹配
(2)局部特化的參數列表不能有缺省實參,但局部特化仍然可以使用基本模板的缺省實參
(3)局部特化的實參或是模板參數,不能是更復雜的依賴型表達式,(諸如2*N,其中N是模板參數)對於非類型實參,或是普通的非類型模板參數。
(4)局部特化的模板實參列表不能和基本模板的參數列表完全相同。(不考慮重新命名)
template<typename T, int I = 3>
class S; //基本模板
template<typename T>
class S<int, T> //錯誤,第二個參數要是int類型的值,參數類型不匹配。
template<typename T = int>
class S<T, 10> //錯誤,不能有缺省實參
template<int I>
class S<int, I*2> //錯誤,局部特化不能有表達式
template<typename U int R>
class S<U, R> //錯誤,跟基本模板沒有實質上區別
編輯整理:Claruarius,轉載請注明出處。