模板

模板

  • 模板,是一個藍圖,是一個與類型無關的函數/類;編譯器在使用模板時,會根據模板實參對模板進行實例化,獲得一個與類型相關的函數/類,以下:node

模板與函數行爲相似性

模板實參

模板形參類型 typename/class,二者徹底同樣 int
模板形參名 引用的是一個類型 引用的是一個編譯期常量表達式
模板形參名的使用位置 能夠在任何須要類型名的位置處使用 能夠在任何須要常量的位置使用
模板形參名的做用域 從被聲明爲模板形參開始,直到,模板聲明/定義的結束

模板的編譯模型

  • C++編譯器爲模板的編譯定義了2種模型:包含模型,分別編譯模型.數組

    • 包含模型: 將模板的聲明與定義都放入頭文件中,在須要使用模板的源文件中包含該頭文件.函數

    • 分別編譯模型: 不考慮測試

  • 若是2個或更多源文件以相同的模板實參使用了同一個模板,則編譯器會爲每個源文件生成一個模板實例,在連接時會去除其餘實例而只保留一個,如:spa

    • 此時可使用 C++11 的外部模板來避免重複實例化,能夠減小編譯與連接時間..net

/* compare.h */
template<typename Type>
const Type& max11(const Type &left,const Type &right){
	return left>right?left:right;
}
/* test.cc */
#include "compare.h"
int	main( int argc,char *argv[] ){
	max11<int>(33,77);
}
/* test2.cc */
#include "compare.h"
void	test2(){
	max11<int>(77,33);
}
/* 'g++ -S test.cc'生成的test.s文件  */
_Z5max11IiERKT_S2_S2_:    /* 編譯器使用int來實例模板獲得的模板實例. */
.LFB2:
	ret
/* 'g++ -S test2.cc'生成的test2.s文件 */
_Z5max11IiERKT_S2_S2_:    /* 編譯器使用int來實例模板獲得的模板實例. */
.LFB2:
	ret

模板的實例化

  1. 編譯器將顯式指定的模板實參賦值相應的模板形參指針

    • 在調用模板函數時,編譯器能夠從函數實參推斷出模板實參,不過強烈建議:顯式指定模板實參.
      code

  2. 根據模板指定的藍圖實例化模板,過程就像文本替換:將全部出現模板形參的位置都用模板實參替換.blog

  3. 編譯實例化後的模板ip

template<typename Type>
int     compare(const Type &left,const Type &right){
    if(left < right)    return -1;
    if(right < left)    return 1;
    return 0;
}

compare<int>(1,3);
/**
 * 1.將模板實參賦值給相應的模板形參: Type = int
 * 2.文本替換:int compare(const int &left,const int &right){ /* 函數體 */ }
 * 3.編譯上述替換後的函數.
 */

模板的特化

  • 由於模板與類型無關,因此會存在這樣一些時刻,模板指定的藍圖並不適用於某種類型,此時的結果多是錯誤的,如:

    • 以上 compare 模板,若調用 compare("Hello","World");則此時Type=const char*;因此此時比較的是兩個字符串地址,而不是字符串自己.

  • 模板特化,對於模板的使用者是透明的,編譯器會在模板實參知足條件時自動調用模板的特化!

模板函數

什麼時候被實例化

  • 當調用模板函數,或者使用模板函數對函數指針進行初始化與賦值時,如: 

typedef		int (*FuncPtr)(const int &,const int &);
template<typename Type>
int 	compare(const Type &lf,const Type &ri){
	Println("Type: %s",typeid(Type).name());
}
int		main(int argc,char *argv[]){
	FuncPtr	ptr=compare;	/* 此時會根據 Type=int 來實例化模板 */
	ptr(33,77);
}
/* ---程序輸出--- */
Type: i;/* i 即 int */

顯式指定模板實參

  • 模板函數,強烈建議應該指定顯式模板實參.

    • 語法: 函數名<模板實參表>(函數實參表)

    • 此時模板實參表中的實參會從左到右依次指定給模板形參.

    • 若是能夠從函數實參中推斷到模板形參對應的模板實參,則最右邊的模板實參能夠省略.如:

template<typename Type1,typename Type2,typename Type3,typename Type4>
void        printTypes(const Type1&,const Type2&,const Type3&,const Type4&){
    PrintType(Type1);
    PrintType(Type2);
    PrintType(Type3);
    PrintType(Type4);
}
class   X{};
int main( int argc,char *argv[] ){
    printTypes<int,double>(1.0,33,X(),1L);
    /**
     * 此時Type1,Type2被顯式指定了模板實參: Type1=int,Type2=double.
     * Type3,Type4是編譯器由函數實參推斷獲得: Type3=X,Type4=long;
     */
}

不顯式指定實參時應該注意的規則

  • 不會轉換實參以匹配現有實例,而是產生新的實例化,此時可能會形成沒有必要的實例,如:

template<typename Type>
int	compare(const Type &,const Type &);

int main( int argc,char *argv[] ){
	int 		i=33,j=77;
	short		si=77,sj=33;

	compare(i,j);			/* 此時以 Type=int 實例化模板.獲得函數:compareIint; */
	compare(si,sj);			/* 此時會以 Type=short 實例化模板.獲得函數:compareIshort; */
	compare<int>(si,sj);	/* 此時調用以前的int實例:compareIint; */
}

  • 若是模板函數的函數形參不是引用類型,則對函數/數組類型的實參應用常規轉換.函數類型形參會被轉換爲函數指針,數組類型的實參會被轉換爲數組指針.

#define PrintType(type) Println(#type ": %s",typeid(type).name());

template<typename Type>
void    pT(Type d){ PrintType(d); }

template<typename Type>
void    pTref(Type &d){ PrintType(d); }

typedef     int (FuncType)(int,char *[]);

int main( int argc,char *argv[] ){
    int a[33];int a1[77];

    PrintType(FuncType*);PrintType(int*);PrintType(FuncType);
    PrintType(int[33]);PrintType(int[77]);

    pT(a);      /* Type=int* */
    pT(a1);     /* Type=int*,調用pT(a)產生的實例; */
    pT(main);   /* Type=FuncType* */

    pTref(a);   /* Type=int[33] */
    pTref(a1);  /* Type=int[77] */
    pTref(main);/* Type=FuncType */
}
/* --- 執行輸出 --- */
FuncType*:  PFiiPPcE
int*:       Pi
FuncType:   FiiPPcE
int[33]:    A33_i
int[77]:    A77_i
d:          Pi
d:          Pi
d:          PFiiPPcE
d:          A33_i
d:          A77_i
d:          FiiPPcE

  • 實參類型必須與特化版本函數的形參類型徹底匹配纔會調用特化版本,不然仍然會依據模板函數從新實例化.特別注意的是 const 修飾,通過以下測試,能夠認爲:

    • 對於指針類型來講,加上 const 與不加 const 被認爲是2種不一樣的類型,如 const char* 與 char* 是2種不一樣的類型.

template<typename Type>
int compare(const Type &left,const Type &right){
    puts("模板");
    if(left > right)    return 1;
    if(left < right)    return -1;
    return 0;
}

typedef const char *ConstCharPtr;
template<>
int compare<ConstCharPtr>(const ConstCharPtr &left,const ConstCharPtr &right){
    puts("特化");
    return strcmp(left,right);
}

template<>
int compare<int>(const int &l,const int &r){
    puts("int");
    return 0;
}

template<>
int compare<const int>(const int &l,const int &r){
    puts("const int");
    return 0;
}

typedef int* int_ptr;
typedef const int* const_int_ptr;

template<>
int compare<int_ptr>(const int_ptr &l,const int_ptr &r){
    puts("int*");
    return 0;
}

template<>
int compare<const_int_ptr>(const const_int_ptr &l,const const_int_ptr &r){
    puts("const int*");
    return 0;
}

int main( int argc,char *argv[] ){
    const char *str1="Hello";const char *str2="World";
    char  cstr1[6]="Hello";char  cstr2[6]="World";
    char *cstr1_ptr = cstr1;
    char *cstr2_ptr = cstr2;
    compare(str1,str2); /* 調用的是特化版本.*/
    compare(cstr1_ptr,cstr2_ptr);  /* 調用的是模板版本,也就是 char* 與 const char* 被認爲是2種不同的類型 */

    int i=3,j=4;
    const int ci=0;
    const int cj=0;
    compare(i,j);/* int */
    compare(ci,cj);/* int;此時 ci 的類型是 const int,但 const 就像被忽略了. */

    const int *i_ptr = &i;
    const int *j_ptr = &j;
    compare(&i,&j);/* int* */
    compare(i_ptr,j_ptr);/* const int*;大概對於指針類型來講,加上 const 與不加上 const 被認爲是2種不一樣的類型. */
}

函數/數組/指針類型

  • 函數/數組/指針類型與基本數據類型是同樣的.如:

/** 基本數據類型 */
int         i;
int         *iptr;
int         &iref;

/* 函數類型,變量,指針,引用的聲明 */
int         (func)(int,char*);
int         (*funcptr)(int,char*);
int         (&funcref)(int,char*);
/* 使用typedef後可能會天然一些 */
typedef int (FuncType)(int,char*);
FuncType    func;
FuncType    *funcptr;
FuncType    &funcref;

/* 數組類型,變量,指針,引用的聲明 */
int         (array)[33];
int         (*arrayptr)[33];
int         (&arrayref)[33];
/* 使用typedef後可能會天然一些 */
typedef int     (ArrayType)[33];
ArrayType       array;
ArrayType       *arrayptr;
ArrayType       &arrayref;

/* 指針類型,變量,指針,引用的聲明 */
char*       ptr;
char*       *ptrptr;
char*       &ptrref;
/* 使用typedef後可能會天然一些 */
typedef     char*   CharPtr;
CharPtr     ptr;
CharPtr     *ptrptr;
CharPtr     &ptrref;

模板特化

  • 語法注意2點:

    • template後接一對空的尖括號(<>);

    • 函數名後可使用<>來指定模板實參.雖然能夠從函數實參推斷出模板實參,但建議顯式指定模板實參.

/**
 * template應該是代表函數是一個模板,空的尖括號<>代表了該模板的模板形參爲空,即該模板顯式指定了模板實參.
 * 因此該模板是一個特化的模板,....
 */
template<>
int     compare<ConstCharPtr>(const ConstCharPtr &left,const ConstCharPtr &right){
    Println("特化");
    return strcmp(left,right);
}

  • 特化模板的聲明應該與對應模板函數的聲明放在一個頭文件中,特化模板的定義應該放入源文件中,主要因爲如下狀況:

    • 在可以聲明/定義特化模板之間,它所特化的模板的聲明必須可見

    • 在調用模板以前,特化的聲明必須可見.

    • 特化模板是一個實實在在的函數實體,就像普通函數同樣,哪怕從未使用過也會被編譯,因此應該放在源文件中.

/* compare.h 存放這模板函數及其特化版本的聲明 */
#ifndef     TEST_COMPARE_H_
#define     TEST_COMPARE_H_
typedef     const char * ConstCharPtr;
/** 模板函數的聲明 */
template<typename Type>
int compare(const Type &,const Type &);

/** 特化版本的聲明 */
template<>
int compare<ConstCharPtr>(const ConstCharPtr &,const ConstCharPtr &);

#include "compare_inl.h"

#endif

/* compare_inl.h 存放這模板函數的定義,模板定義應該放在頭文件中,參見'包含模型' */
#ifndef     TEST_COMPARE_INLINE_H
#define     TEST_COMPARE_INLINE_H

template<typename Type>
int compare(const Type &lf,const Type &ri){ /* 模板函數的定義.. */ }

#endif

/* compare_tehua.cc 存放特化模板函數的定義 */
#include "compare.h"
template<>
int compare<ConstCharPtr>(const ConstCharPtr &lf,const ConstCharPtr &ri){ return strcmp(lf,ri); }

模板類

什麼時候被實例化

  • 模板類的實例化:數據成員的實例化,函數成員的實例化,類型成員的實例化.全部的實例化都是透明的

  • 只要把握一點:僅當成員被使用時,纔會被實例化,注意構造函數,析構函數是被隱式調用的.

顯式指定模板實參

  • 在引用模板類名時必須顯式指定模板的實參,例外:當在模板類自己的做用域內部時,不須要指定模板實參.如:

template<typename Type>
struct	X{
	X	*_ptr;	/* 至關於: X<Type>*_ptr */
public:
	X(){ ; }
	X(const X &);	/* 至關於: const X<Type> & */
};

template<typename Type>
X<Type>::X(const X &){ Println("%s",typeid(X).name()); }
/**
 * 當編譯器看到被徹底限定的函數名時,她就將函數移到限定名所指定的做用域.
 * 因此此時參數'X'也不須要指定類型.
 */

類型成員

  • 類的成員: 數據成員,函數成員,類型成員

  • 類型成員中也可使用模板類的模板形參,正如數據成員,函數成員中使用同樣.如: 

template<typename ElemT>
class Queue{
struct  _QueueItem{ /** Queue內部的嵌套類.是 Queue 的類型成員 */
    ElemT               _data;/* 使用模板形參 ElemT 定義數據 */
    _QueueItem          *_next;
};
public:
    /** value_type 也是 Queue 的類型成員. */
    typedef     ElemT       value_type;
private:
    _QueueItem  *_head;
    _QueueItem  *_tail;
};

  • 若在模板類外訪問模板類的類型成員,須要加上前綴 typename 做爲修飾.如:

template<typename Type>
void    addElem(Queue<Type> &queue,const Type &item){
    Queue<Type>::_QueueItem             *newItem;    /* 錯誤 */
    /**
     * 此時不到最後實例化時刻,都不能肯定 _QueueItem 到底是什麼.
     * 由於能夠將 _QueueItem 解釋爲 Queue<Type> 的靜態數據成員.此時代表將該靜態數據成員與 newItem 相乘
     * 一樣能夠將 _QueueItem 解釋爲 Queue<Type> 的類型成員.此時代表聲明一個 newItem 變量
     */
    typename Queue<Type>::_QueueItem    *newItem;   /* 正確 */
}

int main( int argc,char *argv[] ){
    Queue<int>::_QueueItem  *node;
    /**
     * 此時 Queue<int> 已被實例化,能夠查詢 Queue<int> 類的定義(由編譯器根據模板類定義的藍圖生成)
     * 肯定 _QueueItem 是 Queue<int> 類的類型成員,因此不須要使用 typename 前綴.
     */
    Queue<int>::value_type  *value;
}

模板類的友元聲明

  • 將非模塊函數,非模板類聲明爲友元,此時並不須要在friend以前聲明類/函數,friend自己具備聲明的效果.如:

class        Y;        /* 能夠不聲明 */
FuncType     main;     /* 能夠不聲明 */   
class   X{
    friend      class       Y;
    friend      FuncType    main;   /* 將 main 函數設爲友元 */
};

  • 將模板函數/模板類的全部實例聲明爲友元,此時也不須要提早對友元類,友元函數進行聲明..

class   X{
    template<typename Type> friend int compare(const Type&,const Type&);
    template<typename Type> friend class Y; /* 模板類Y的全部實例中的全部成員均可以訪問X的private,protected成員 */
private:
    int i;
};

  • 將模板函數/模板類的指定實例聲明爲友元,此時須要提早對類/函數進行聲明,如:

template<typename Type> int compare(const Type&,const Type&);
template<typename Type> class Y;

class	X{
	friend int compare<int>(const int&,const int&);/* 只有compare<int>實例是X的友元函數 */
	friend class Y<double>;
};

template<typename Type>
class    Z{
friend    class Y<Type>; /* Y<int>是Z<int>的友元類,但不是Z<double>的友元類 */
};

模板類的模板成員

  • 關鍵是模板成員的類外定義,應該有2個模板形參表

    • 位於左側的模板形參表,表示模板類的模板形參

    • 位於右側的模板形參表,表示模板成員自身的模板形參,

template<typename Type1>
class   X{
    static  Type1   _data;  /* 靜態數據成員,類型能夠是模板形參. */
public:
    template<typename Type2>    /* 代表 func 是一個模板 */
    static void func();
};

template<typename Type1>
Type1   X<Type1>::_data;    /* 類外定義靜態成員. */

template<typename Type1>    template<typename Type2>
void        X<Type1>::func(){ PrintType(Type1);PrintType(Type2); }

int main( int argc,char *argv[] ){
    X<int>  x;
    x.func<double>();       /* 由x的類型肯定Type1=int;顯式指定:Type2=double */
    X<double>::func<int>();/* 顯式指定Type1=double,Type2=int */
}

  • 靜態數據成員的類型能夠是模板形參名.如上;以及類外定義靜態數據成員.

模板類的特化

到達字數上界,參見 模板-2-模板類的特化

相關文章
相關標籤/搜索