1. 程式人生 > 程式設計 >深入分析C++模板特化與偏特化

深入分析C++模板特化與偏特化

1.模板特化

1.1概述

模板特化(template specialization)不同於模板的例項化,模板引數在某種特定型別下的具體實現稱為模板的特化。模板特化有時也稱之為模板的具體化,分別有函式模板特化和類模板特化。

1.2函式模板特化

函式模板特化是在一個統一的函式模板不能在所有型別例項下正常工作時,需要定義型別引數在例項化為特定型別時函式模板的特定實現版本。檢視如下例子。

#include <iostream>
using namespace std;

template<typename T> T Max(T t1,T t2)
{
return (t1>t2)?t1:t2;
}

typedef const char* CCP;
template<> CCP Max<CCP>(CCP s1,CCP s2)
{
return (strcmp(s1,s2)>0)?s1:s2;
}

int main()
{
//呼叫例項:int Max<int>(int,int)
int i=Max(10,5);
//呼叫顯示特化:const char* Max<const char*>(const char*,const char*)
const char* p=Max<const char*>("very","good");
cout<<"i:"<<i<<endl;
cout<<"p:"<<p<<endl;
}

程式正常編譯執行結果:

i:10
p:very

在函式模板顯示特化定義(Explicit Specialization Definition)中,顯示關鍵字template和一對尖括號<>,然後是函式模板特化的定義。該定義指出了模板名、被用來特化模板的模板實參,以及函式引數表和函式體。在上面的程式中,如果不給出函式模板Max< T>在T為const char*時的特化版本,那麼在比較兩個字串的大小時,比較的是字串的起始地址的大小,而不是字串的內容在字典序中先後次序。

除了定義函式模板特化版本外,還可以直接給出模板函式在特定型別下的過載形式(普通函式)。使用函式過載可以實現函式模板特化的功能,也可以避免函式模板的特定例項的失效。例如,把上面的模板特化可以改成如下過載函式。

typedef const char* CCP;
CCP Max(CCP s1,s2)>0)?s1:s2;
}

程式執行結果和使用函式模板特化相同。但是,使用普通函式過載和使用模板特化還是有不同之處,主要表現在如下兩個方面:
(1)如果使用普通過載函式,那麼不管是否發生實際的函式呼叫,都會在目標檔案中生成該函式的二進位制程式碼。而如果使用模板的特化版本,除非發生函式呼叫,否則不會在目標檔案中包含特化模板函式的二進位制程式碼。這符合函式模板的“惰性例項化”準則。
(2)如果使用普通過載函式,那麼在分離編譯模式下,應該在各個原始檔中包含過載函式的申明,否則在某些原始檔中就會使用模板函式,而不是過載函式。

1.3類模板特化

類模板特化類似於函式模板的特化,即類模板引數在某種特定型別下的具體實現。考察如下程式碼。

#include <iostream>
using namespace std;

template<typename T>class A
{
T num;
public:
A()
{
num=T(6.6);
}
void print()
{
cout<<"A'num:"<<num<<endl;
}
};

template<> class A<char*>
{
char* str;
public:
A(){
str="A' special definition ";
}
void print(){
cout<<str<<endl;
}
};

int main()
{
A<int> a1; //顯示模板實參的隱式例項化
a1.print();
A<char*> a2; //使用特化的類模板
A2.print();
}

程式輸出結果如下:

A'num:6
A' special definition

2.模板偏特化

2.1概述

模板偏特化(template partitial specialization)是模板特化的一種特殊情況,指指定模板引數而非全部模板引數,或者模板引數的一部分而非全部特性,也稱為模板部分特化。與模板偏特化相對的是模板全特化,指對所有的模板引數進行特化。模板全特化與模板偏特化共同組成模板特化。

模板偏特化主要分為兩種,一種是指對部分模板引數進行全特化,另一種是對模板引數特性進行特化,包括將模板引數特化為指標、引用或是另外一個模板類。

2.2函式模板偏特化

假如我們有一個compare函式模板,在比較數值型別時沒有問題,如果傳入的數值的地址,我們需要兩個數值的大寫,而非比較傳入的地址大小。此時我們需要對compare函式模板進行偏特化。考察如下程式碼:

#include <vector>
#include <iostream> 
using namespace std;

//函式模板
template<typename T,class N> void compare(T num1,N num2)
{
cout << "standard function template" << endl;
if(num1>num2)
cout << "num1:" << num1 << " > num2:" << num2 <<endl;
else
cout << "num1:" << num1 << " <= num2:" << num2 << endl;
}

//對部分模板引數進行特化
template<class N> void compare(int num1,N num2)
{
cout<< "partitial specialization" <<endl;
if (num1>num2)
cout << "num1:" << num1 << " > num2:" << num2 << endl;
else
cout << "num1:" << num1 << " <= num2:" << num2 << endl;
}

//將模板引數特化為指標
template<typename T,class N> void compare(T* num1,N* num2)
{
cout << "new partitial specialization" << endl;
if (*num1>*num2)
cout << "num1:" << *num1 << " > num2:" << *num2 << endl;
else
cout << "num1:" << *num1 << " <= num2:" << *num2 << endl;
}

//將模板引數特化為另一個模板類
template<typename T,class N> void compare(std::vector<T>& vecLeft,std::vector<T>& vecRight)
{
cout << "to vector partitial specialization" << endl;
if (vecLeft.size()>vecRight.size())
cout << "vecLeft.size()" << vecLeft.size() << " > vecRight.size():" << vecRight.size() << endl;
else
cout << "vecLeft.size()" << vecLeft.size() << " <= vecRight.size():" << vecRight.size() << endl;
}

int main()
{
compare<int,int>(30,31);//呼叫非特化版本compare<int,int>(int num1,int num2)

compare(30,'1'); //呼叫偏特化版本compare<char>(int num1,char num2)

int a = 30;
char c = '1';
compare(&a,&c); //呼叫偏特化版本compare<int,char>(int* num1,char* num2)

vector<int> vecLeft{0};
vector<int> vecRight{1,2,3};
compare<int,int>(vecLeft,vecRight); //呼叫偏特化版本compare<int,char* num2)
}

程式輸出結果如下:

standard function template
num1:30 <= num2:31
partitial specialization
num1:30 <= num2:1
new partitial specialization
num1:30 <= num2:1
to vector partitial specialization
vecLeft.size()1 <= vecRight.size():3

2.3類模板偏特化

類模板的偏特化與函式模板的偏特化類似。考察如下程式碼:

#include <vector>
#include <iostream> 
using namespace std;

//類模板
template<typename T,class N> class TestClass
{
public:
static bool comp(T num1,N num2)
{
cout <<"standard class template"<< endl;
return (num1<num2) ? true : false;
}
};

//對部分模板引數進行特化
template<class N> class TestClass<int,N>
{
public:
static bool comp(int num1,N num2)
{
cout << "partitial specialization" << endl;
return (num1<num2) ? true : false;
}
};

//將模板引數特化為指標
template<typename T,class N> class TestClass<T*,N*>
{
public:
static bool comp(T* num1,N* num2)
{
cout << "new partitial specialization" << endl;
return (*num1<*num2) ? true : false;
}
};

//將模板引數特化為另一個模板類
template<typename T,class N> class TestClass<vector<T>,vector<N>>
{
public:
static bool comp(const vector<T>& vecLeft,const vector<N>& vecRight)
{
cout << "to vector partitial specialization" << endl;
return (vecLeft.size()<vecRight.size()) ? true : false;
}
};

int main()
{
//呼叫非特化版本
cout << TestClass<char,char>::comp('0','1') << endl; 

//呼叫部分模板引數特化版本
cout << TestClass<int,char>::comp(30,'1') << endl; 

//呼叫模板引數特化為指標版本
int a = 30;
char c = '1';
cout << TestClass<int*,char*>::comp(&a,&c) << endl; 

//呼叫模板引數特化為另一個模板類版本
vector<int> vecLeft{0};
vector<int> vecRight{1,3};
cout << TestClass<vector<int>,vector<int>>::comp(vecLeft,vecRight) << endl; 
}

程式輸出結果:

standard class template
1
partitial specialization
1
new partitial specialization
1
to vector partitial specialization
1

3.模板類呼叫優先順序

對主版本模板類、全特化類、偏特化類的呼叫優先順序從高到低進行排序是:全特化類>偏特化類>主版本模板類。這樣的優先順序順序對效能也是最好的。

但是模板特化並不只是為了效能優化,更多是為了讓模板函式能夠正常工作,最典型的例子就是STL中的iterator_traits。algorithm中大多數演算法通過iterator物件來處理資料,但是同時允許以指標代替iterator物件,這是為了支援C-Style Array。如果直接操作iterator,那麼為了支援指標型別,每個演算法函式都需要進行過載,因為指標沒有::value_type型別。為了解決這個問題,STL使用了iterator_traits對iterator特性進行封裝,併為指標型別做了偏特化處理,演算法通過它來操作iterator,不需要知道實際操作的是iterator物件還是指標。

template<typename IteratorClass> class iterator_traits
...
template<typename ValueType> class iterator_traits<ValueType*>
...
template<typename ValueType> class iterator_traits<ValueType const*>
...

後面兩是針對指標型別的偏特化,也是偏特化的一種常見形式。

以上就是深入分析C++模板特化與偏特化的詳細內容,更多關於C++模板特化與偏特化的資料請關注我們其它相關文章!