函數模板 Function Template(C++Primer-10)

來源:互聯網
上載者:User

10 函數模板

10.1 函數模板定義

template <typename A, typename B, int size> A Func(const B(&rArray)[size]) { return A();}

>關鍵字template後面是用逗號分隔的模板參數表template parameter list, 用<>包括起來; 模板參數列表不可為空;(特化時可以); 

模板參數可以為模板型別參數template type parameter,代表一種類型; 也可以是模板非型別參數template nontype parameter, 代表一個常量運算式;

一般情況角括弧<>中的typename代表C++內建資料類型, class代表C++資料類型(自訂);

template <class Type, int size>Type min(const Type(&rArray)[size]) //SIZE作為參數一部分, 編譯器會檢查實參長度是否匹配{    Type minVal = rArray[0];    for(int i=0; i<size; i++)    {        if(rArray[i]<minVal)            minVal = rArray[i];    }    return minVal;}//---const int size = 5;int iArray[size] = {5, 7, 3, 22, 99};cout<<min(iArray)<<endl;

>類型和值的替換過程稱為模板執行個體化template instantiation;

模板型別參數被當作一個類型標識符, 使用方式和內建或使用者定義型別一樣; e.g.聲明變數, 強制類型轉換;
模板非型別參數被用作常量值; e.g.數組大小, 枚舉常量初值;

Note 如果在全域域中聲明了與模板參數同名的對象,,函數或類型, 則該全域名將被隱藏; 
       函數模板定義中聲明的對象或類型不能與模板參數同名;(無效或error);

模板型別參數名名可以用來指定函數模板的傳回值; 模板參數名在同一模板參數表中只能被使用一次, 但是模板參數名可以在多個函數模板聲明或定義之間重複使用; 一個模板的定義和多個聲明所使用的模板參數不需要相同; 模板參數在函數參數表中出現的次數沒有限制; 每個模板型別參數前面都必須有關鍵字class或typename;

函數模板參數表中typename和class意義相同; 都可以用來聲明模板參數表中的不同模板型別參數;<<Design and Evolution of C++>>    

Note 為了讓編譯器能夠分析模板定義, 使用者必須指示編譯器, 哪些運算式是類型運算式: typename;

template <class Parm, class U>Parm minus(Parm* array, U value){    typename Parm::name* p;//指標聲明    Parm::name* p; //編譯器不知道這是指標聲明還是乘法}

Note 函數模板聲明為inline或extern時, 指示符放在模板參數表後面, 不能放在關鍵字template前面;

10.2 函數模板執行個體化

函數模板指定實際類型或值構造出獨立函數的過程: 模板執行個體化template instantiation;

這個過程是隱式發生的, 可看作是函數模板被調用或取函數模板的地址的副作用;

int (*pf)(int (&)[10]) = &min;// min(int(&)[10]);

編譯器會檢查函數調用中提供的函數實參的類型. 用函數實參的類型來決定模板實參的類型和值的過程被稱為模板實參推演template argument deduction;

Note 在取函數模板執行個體的地址時, 必須通過上下文為模板實參決定一個唯一的類型或值;

typedef int (&rai)[10];typedef double (&rad)[20];void func( int (*)(rai) ){}void func( double (*)(rad) ){}//---func(&min);//error, overload-function

>編譯錯誤: func()被重載了, 編譯器無法決定Type的唯一類型, 也無法決定size的唯一值; 調用func()無法執行個體化函數;

10.3 模板實參推演

函數模板調用時, 對函數實參類型的檢查決定模板實參的類型和值, 這個過程稱為模板實參推演template argument deduction;

>函數實參必須是一個數群組類型的左值; 這裡的pval是函數參數, 所以轉換為指標變數, 與數組左實值型別不匹配; (傳參數時數組->指標匹配, 指標->數組不行)

template <class Type, int size>Type min( Type (&r_array)[size] ) { /* ... */ }void f( int pval[9] ) {// 錯誤: Type (&)[] != int*//int jval = min( pval );}

模板實參推演時, 函數的實參類型不一定要嚴格匹配函數參數類型;
允許的類型轉換: 左值轉換, 限定轉換, 到一個基類的轉換;

左值轉換包括: 左值到右值的轉換, 數組到指標的轉換, 函數到指標的轉換;

函數模板實參推演的通用演算法:
1)依次檢查每個函數實參, 確定在每個函數實參的類型中出現的模板參數;
2)找到模板參數, 通過檢查函數實參的類型, 推演相應的模板實參;
3)函數參數類型和函數實參類型可以轉換: -左值轉換 -限定修飾符轉換 - 衍生類別到基類的轉換;
4)如果在多個函數參數中找到同一個模板參數, 從每個相應函數實參推演出的模板參數必須相同(模板參數會被綁定在第一個類型上)


10.4 顯示模板實參

顯示指定explicitly specify 模板實參;

e.g. func<usigned int>(1024); //1024被有序標準轉換成unsigned int類型;

當函數模板實參被顯示指定時, 函數實參轉換成相應函數參數的類型可以應用任何隱式類型轉換;

顯式特化explicit specification, 只能省略尾部的實參;

char ch; unsigned int ui;template <class T1, class T2, class T3>T1 sum( T2, T3 );typedef unsigned int ui_type;ui_type loc1 = sum( ch, ui );//error, T1不能被推演ui_type loc2 = sum< ui_type, char, ui_type >( ch, ui );//okui_type loc3 = sum< ui_type, char >( ch, ui );//okui_type (*pf)( char, ui_type ) = &sum< ui_type >;//okui_type loc4 = sum< ui_type, , ui_type >( ch, ui );//error, 只能省略尾部的實參

>對於重載函數的函數指標類型實參, 為避免二義性, 使用顯式指定;

template <class T1, class T2, class T3>T1 sum( T2 op1, T3 op2 ) { /* ... */ }void manipulate( int (*pf)( int,char ) );void manipulate( double (*pf)( float,float ) );int main( ){    manipulate( &sum );// 錯誤: 二義    manipulate( &sum< double, float, float > );// 調用: void manipulate( double (*pf)( float, float ) );}

建議在可能的情況下省略顯式模板實參, 讓函數功能更泛化;

Note 傳回值無法推演, 推演只作用在參數上, 如果傳回值和參數相同, 則可以被間接推演;


10.5 模板編譯模式

C++模板編譯模式 template compilation model 指定了對於定義和使用模板的程式的組織方式的要求; 
編譯模式: 包含模式 Inclusion Model 和 分離模式 Separation Model;

10.5.1 包含編譯模式

模板定義在標頭檔中, 使用模板執行個體前包含模板定義;

缺點: 模式模板體body描述了實現細節, 這些是我們希望對使用者隱藏的;
       函數模板定義很大的情況下, 標頭檔中的細節層次變得不可接受;
       多個標頭檔之間編譯相同的函數模板定義增加編譯時間;
10.5.2 分離編譯模式

函數模板聲明在標頭檔中; 

// model2.h // 分離模式: 只提供模板聲明template <typename Type> Type min( Type t1, Type t2 );// model2.C // the template definitionexport template <typename Type>Type min( Type t1, Type t2 ) { /* ...*/ }// user.C#include "model2.h"int i, j;double d = min( i, j ); //用法, 需要一個執行個體

>關鍵字export告訴編譯器在產生被其他檔案使用的函數模板執行個體時需要這個模板定義(可匯出的函數模板), 編譯器需要保證模板定義是可見的;
(有些編譯器可能不要求export; 標準C++要求在template之前標記export)

關鍵字export在標頭檔的模板聲明中不是必需的;(在cpp實現中需要)

一個模板函數只能被定義為export一次; 編譯器每次只處理一個檔案, 無法檢測到模板函數在多個文字檔中定義為export的情況; 可能發生的情況:
1)連結錯誤, 函數模板在多個檔案中被定義;
2)編譯器多次為同一個模板實參集合執行個體化函數模板, 函數模板執行個體重複定義, 連結錯誤;
3)編譯器可能只用其中一個export函數模板定義執行個體化函數模板, 忽略其他定義;

Note 不是所有的編譯器都支援分離模式; (VC2010, g++不支援)

10.5.3 顯式執行個體化聲明

顯式執行個體化聲明協助控制並減少編譯器對模板多次執行個體化的情況, 減少編譯時間; 

template <typename Type>Type sum( Type op1, int op2 ) { /* ... */ }template int* sum<int*>(int*, int);

顯式執行個體化聲明在程式中只能出現一次, 函數模板的定義必須可見;

顯式執行個體化聲明是與一個編譯選項聯合使用的, 該選項壓製程序中模板的隱式執行個體化; e.g. /ft-; (這個模式下顯式執行個體化聲明是必需的)


10.6 模板顯式特化

模板顯式特化定義explicit specialization definition;

template <class T>T max( T t1, T t2 ) {    return (t1 > t2 ? t1 : t2);}//特化 specializetypedef const char *PCC;template<> PCC max< PCC >( PCC s1, PCC s2 ) {    return ( strcmp( s1, s2 ) > 0 ? s1 : s2 );}

>由於有了顯式特化, 程式在調用max()時模板不對const char*類型進行執行個體化, 而是直接調用特化的定義;

如果模板實參可以從函數參數中推演出來, 模板實參的顯式特化可以省略

template<> PCC max( PCC s1, PCC s2 )

>省略角括弧"<>"部分;

使用函數模板特化時, 必須能讓所有使用特化的檔案看到其聲明, 否則編譯器找不到特化, 會執行個體化基模板; 
如果同一程式在一個檔案中執行個體化函數模板執行個體(聲明不可見), 在另一個檔案中又調用其顯式特化, 程式非法;

Note: 顯式特化的聲明應該被放在標頭檔中, 並且在所有使用函數模板的程式中包含此標頭檔;


10.7 重載函數模板

重載的函數模板可能會導致二義性;

template <typename T>int min5( T, T ) { /* ... */ }template <typename T, typename U>int min5( T, U );

>對於類似min(1024, 1); 這樣的調用, T和U可以是同為int型, 這樣兩個模板都可以被執行個體化; 需要顯式指定模板實參防止二義性錯誤;

>min5(T,U);處理的調用集是由min5(T,T)處理的超集, 所以只提供min5(T,U)的聲明就可以, min5(T,T)應該刪除;

最特化most specialized 相同模板名字和參數個數; 對於不同類型的相應函數參數, 一個模板是另一個的超集;

template <typename Type> Type sum( Type*, int );//1)template <typename Type> Type sum( Type, int );//2)int ia[1024];// Type == int ; sum<int>( int*, int ); or// Type == int*; sum<int*>( int*, int ); ??int ival1 = sum<int>( ia, 1024 );

>1)更特化; 2)是1)的超集;


10.8 考慮模板函數執行個體的重載解析

函數模板可以被重載, 函數模板可以與普通函數同名;

// 普通函數int min( int a1, int a2 ) {    min<int>( a1, a2 );}

>通過普通函數, 無論何時使用整型實參, 程式都調用min(int,int)的特化版本;

普通函數和函數模板的函數重載解析步驟:

1)產生候選函數集; 
考慮與函數調用同名的函數模板, 如果模板實參推演成功則執行個體化一個函數模板, 則該模板作為一個候選函數;

2)產生可行函數集;
保留候選函數集中可以調用的函數;

3)對類型轉換劃分等級;
a)如果只選擇了一個函數, 則調用該函數; b)如果調用是二義的, 則從可行函數集中去掉函數模板執行個體;

4)只考慮可行函數集中的普通函數, 完成重載解析過程;
a)如果只選擇了一個函數, 則調用該函數; b)否則是二義的;


10.9 模板定義中的名字解析

類型依賴於模板參數depend on a template parameter;

模板定義中的名字解析分兩個步驟進行:
首先, 不依賴於模板參數的名字, 在模板定義時被解析; (普通函數 e.g. print("string");)
其次, 依賴於模板參數的名字, 在模板被執行個體化時被解析; (依賴於模板的函數; e.g. print(Type t);)

Note 函數模板的設計者必須確保為模板定義中用到的, 所有不依賴於模板參數的名字提供聲明; 否則編譯錯誤;

在原始碼中模板被執行個體化的位置稱為模板的執行個體化點point of instantiation; 
如果模板執行個體要多次使用, 編譯器自由選擇這些執行個體化點之一來真正執行個體化該函數模板; 因此在模板的任何一個執行個體被使用之前, 應該在標頭檔中給出所有必須的聲明;


10.10 名字空間和函數模板

函數模板定義可以放在名字空間中;

namespace cplusplus_primer {// 模板定義被隱藏在名字空間中template<class Type>Type min( Type* array, int size ) { /* ... */ }}int ai[4] = { 12, 8, 73, 45 };int size = sizeof(ai) / sizeof(ai[0]);using cplusplus_primer::min; // using 聲明min( &ai[0], size );

作為包含模板定義的庫的使用者, 如果想為庫中的模板提供特化, 必須保證它們的定義被合適地放置在含有原始模板定義的名字空間內;
1)把模板特化放在基模板所在的名字空間內;
2)用外圍名字空間名修飾名字空間成員名;

template<> SmallInt cplusplus_primer::min<SmallInt>( SmallInt* array, int size ){ /* ... */ }


10.11 函數模板樣本

快速排序sort();

#include "Array.h"template <class elemType>void sort( Array<elemType> &array, int low, int high ) {    if ( low < high ) {        int lo = low;        int hi = high + 1;        elemType elem = array[lo];//基數        for (;;) {            while ( min( array[++lo], elem ) != elem && lo < high ) ;            while ( min( array[--hi], elem ) == elem && hi > low ) ;        if (lo < hi)            swap( array, lo, hi );//較大數放右邊, 較小數放左邊        else break;        }        swap( array, low, hi );//基數放中間        //分治遞迴        sort( array, low, hi-1 );        sort( array, hi+1, high );    }}

---End---

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.