# C++ template常用功能
函式範本(Function template)又稱「通用函式」(Generic function),它可以適用於不同資料型態的參數列,但實作內容相同的函式.
在template中可以使用關鍵字class或typename,兩者功能相同只是名稱不同而已。
- Scott Myers, Effective C++ (3rd ed.) item 42中建議如果類別是class時,使用關鍵字class,如果是原生類別如int, float等,使用typename。
template <typename T>
T max (const T& a, const T& b) {
return (a>b)? a : b;
}
不可將template的宣告和實作放在不同檔案
/*** test.h ***/
template <class T>
T MaxTest(T x, T y);
/*** test.cpp ***/
template <typename T>
T MaxTest (const T& a, const T& b) {
return (a>b)? a : b;
}
/ *** main.cpp ***/
#include <iostream>
#include "test.h"
using namespace std;
int main(){
cout<<MaxTest(3, 4)<<endl;
return 0;
}
// compile
g++ -o test main.cpp
// 會出現以下錯誤訊息
main.cpp:(.text+0xf): 未定義參考到「int MaxTest<int>(int, int)」
collect2: error: ld returned 1 exit status
首先,C++中一個編譯單元(translation unit)是指一個 .cpp 以及它所 include 的所有標頭檔 .h 文件,標頭檔裡的所有程式碼會被擴展到包含它的 cpp 檔裡,然後編譯器編譯該 cpp 檔成一個 obj 檔案。
- obj 是二進位碼,但是不一定能夠執行,因為不能保證裡面有 main 函式。
- 當編譯器把各別的 cpp 檔都編譯成 obj 檔後,再由連結器(linker)連結成一個執行檔(.exe)。
對於非template的函式MaxTest簡單來說,編譯 main.cpp 時,編譯器不知道 MaxTest 函式的實作程式碼在哪裡,所以遇到 MaxTest 函式時只是先給個指示,指示連結器當找到 MaxTest 實作程式碼實再來取代。
- 編譯 test.cpp 時,編譯器找到 MaxTest 函式的實作程式碼,並編在 test.obj 裡。
- 連結時,就把剛剛 main.obj 裡懸而未決的位址換成 MaxTest 實作程式碼的真正位址。
但是 template 比較特別,是在呼叫 template function 時,C++ 編譯器依據引數的資料型態,「自動」產生出所需的函式,這個過程稱為實例化(instantiate)。
- 在main.cpp中呼叫的是整數類別的int MaxTest(int x, int y),因為它不在 test.h 裡,只好希望連結器可以在別的 obj 裡找到。
- 但是 test.cpp 裡的是T MaxTest(T x, T y),而不是 int MaxTest(int x, int y),所以根本就沒有 int MaxTest(int x, int y) 的實作程式碼,因此連結錯誤。
關鍵在於 template 只有在需要時才會被實例化出來,所以當編譯器只看到 template 的宣告時,他不能實例化該 template,只能先建立一個外部連結。然而當實現該 template 的 cpp 沒有用到實例化時,編譯器就無法實例化,所以連結失敗。
解決template實例化的問題
- 把實作程式碼寫在 .h 檔裡
- 在 main.cpp 裡 include "test.cpp"
- 可以不用把 include "test.cpp" 寫在 main.cpp 裡,那就是寫在 test.h 裡,要特別注意多重 include 的問題。
#ifndef TEST_H
#define TEST_H
template <class T>
T MaxTest(T x, T y);
#include "test.cpp"
#endif
樣板特製化(template Specialization)
- 同樣名稱的樣板函式或類別可以重複出現,只要針對某個type進行特製化這是為了有時候我們需要對某些Type作"特別"的處理。
template <class T>
void swaps(T& a1,T& a2);
// 特製化樣板 如果傳近來int就什麼都不做
template <>
void swaps<int>(int& a1,int& a2){}
多樣別或帶參數樣板
//帶一個參數
template<class T,int Size>
void print(T t) { cout<<t<<":"<<Size<<endl; }
// 兩個class
template<class T, class U>
void compare(T t, U u){...}
樣版類別(tempalte class)
- 在開頭宣告的template其有效範圍為整個class
- template是不能分開成.h和.cpp,如果要把實作分開也要全部再.h檔裏面完成或者用include方式。
// printer.h
template <class T>
class Printer {
T _t;
public:
Printer(T t){_t=t;}
void print(){ cout<<_t<<endl;}
};
// printer.cpp
template <class T>
Printer<T>::Printer(T t){
_t=t;
}
template <class T>
void Printer<T>::print(){
cout<<_t<<endl;
}