模板(Template)指C++程序設計設計語言中采用類型作為參數的程序設計,支持通用程序設計。C++ 的標准庫提供許多有用的函數大多結合了模板的觀念,如STL以及IO Stream。 參考:https://www.jianshu.com/p/31d7e18372e2
函數模板定義一族函數。
//template1.cpp #include <iostream>
template<typename T> void swap(T &a, T &b) {
T tmp{a}; a = b;
b = tmp;
}
int main(int argc, char* argv[])
{
int a = 2; int b = 3;
swap(a, b); // 使用函數模板
std::cout << "a=" << a << ", b=" << b << std::endl;
double c = 1.1; double d = 2.2; swap(c, d);std::cout << "c=" << c << ", d=" << d << std::endl;return 0;
}
函數模板的格式:
template<parameter-list> function-declaration
函數模板在形式上分為兩部分:模板、函數。在函數前面加上 template<...>就成為函數模板,因此對函數的各種修飾(inline、constexpr 等)需要加在 function-declaration 上,而不是 template 前。如
template
inline T min(const T &, const T &);
parameter-list 是由英文逗號(,)分隔的列表,每項可以是下列之一:

上面 swap 函數模板,使用了類型形參。函數模板就像是一種契約,任何滿足該契約的類型都可以做為模板實參。而契約就是函數實現中,模板實參需要支持的各種操作。上面
swap 中 T 需要滿足的契約為:支持拷貝構造和賦值。
C++ 的函數模板本質上函數的重載,泛型只是簡化了程序員的工作,讓這些重載通過編譯器來完成。我們可以用 c++flit 來觀察這一現象。

通過 objdump 可以把 template1.out 中代碼段的與 swap 相關的符號提出來,有兩個, 分別是_Z4swapIdEvRT_S1_和_Z4swapIiEvRT_S1_
再用 f++filt 將 Name Mangling 后的名字翻轉過來,就對應了兩個函數原型:
void swap(double&, double&)
void swap(int&, int&)
1.2 函數模板不是函數
剛才我們提到函數模板用來定義一族函數,而不是一個函數。C++是一種強類型的語
言,在不知道 T 的具體類型前,無法確定 swap 需要占用的棧大小(參數棧,局部變量), 同時也不知道函數體中 T 的各種操作如何實現,無法生成具體的函數。只有當用具體
類型去替換 T 時,才會生成具體函數,該過程叫做函數模板的實例化。當在 main 函數中調用 swap(a,b)時,編譯器推斷出此時 T 為 int,然后編譯器會生成 int 版的 swap 函數供調用。所以相較普通函數,函數模板多了生成具體函數這一步。如果我們只是編
寫了函數模板,但不在任何地方使用它(也不顯式實例化),則編譯器不會為該函數模板生成任何代碼。函數模板實例化分為隱式實例化和顯式實例化。

1.3 隱式實例化 implicit instantiation
仍以 swap 為例,我們在main 中調用 swap(a,b)時,就發生了隱式實例化。當函數模板被調用,且在之前沒有顯式實例化時,即發生函數模板的隱式實例化。如果模板實參能從調用的語境中推導,則不需要提供。效率較低。
//template2.cpp #include
template void print(const T &r) {
std::cout << r << std::endl;
}
int main() {
// 隱式實例化print(int) print(1);
// 實例化 print(char) print<>('c');
// 仍然是隱式實例化,我們希望編譯器生成print(double) print(1);
return 0;
}
1.4 顯式實例化 explicit instantiation
隱式實例化可能影響效率,所以需要提高效率的顯式實例化,顯式實例化在編譯期間就會生成實例(增加了編譯時間)。在函數模板定義后,我們可以通過顯式實例化的方式告訴編譯器生成指定實參的函數。顯式實例化聲明會阻止隱式實例化。如果我們在顯式實例化時,只指定部分模板實參,則指定順序必須自左至右依次指定,不能越過前參模板形參,直接指定后面的。



1.5. 模板函數的隱式/顯示化與特化辨析
如果你真的想要實例化(而不是特殊化或某物)的功能,請執行以下操作:
template void func(T param) {} // definition
template void func(int param); // explicit instantiation.
template <> void func(int param) {} // specialization
總結一下,C++只有模板顯式實例化(explicit instantiation),隱式實例化(implicit instantiation),特化(specialization,也譯作具體化,偏特化)。首先考慮如下模板函數代碼:
template
void swap(T &a, T &b){
...
}
1.隱式實例化
我們知道,模板函數不是真正的函數定義,他只是如其名提供一個模板,模板只有在運行時才會生成相應的實例,隱式實例化就是這種情況:
int main(){
....
swap(a,b);
....
}
它會在運行到這里的時候才生成相應的實例,很顯然的影響效率這里順便提一下 swap<int>(a,b);中的<int>是可選的,因為編譯器可以根據函數參數類型自動進行判斷,也就是說如果編譯器不不能自動判斷的時候這個就是必要的;
2.顯式實例化
前面已經提到隱式實例化可能影響效率,所以需要提高效率的顯式實例化,顯式實例化在編譯期間就會生成實例,方法如下:
template void swap(int &a,int &b);
這樣就不會影響運行時的效率,但編譯時間隨之增加。
3.特化
這個 swap 可以處理一些基本類型如 long int double,但是如果想處理用戶自定義的類型就不行了,特化就是為了解決這個問題而出現的:
template <> void swap(job a,job b){...}
其中 job 是用戶定義的類型.
2. 函數模板的使用
2.1 使用非類型形參
//template3.cpp
#include
// N 必須是編譯時的常量表達式
template
void printArray(const T (&a)[N]) {
std::cout << "[";
const char *sep = "";
for (int i = 0; i < N; i++, (sep = ", ")) {
std::cout << sep << a[i];
}
std::cout << "]" << std::endl;
}
int main() {
// T: int, N: 3
int a[]={1, 2, 3};
printArray(a);
float b[] = {1.1, 2.2, 3.3};
printArray(b);
return 0;
}

2.2返回值為 auto
有些時候我們會碰到這樣一種情況,函數的返回值類型取決於函數參數某種運算后的類型。對於這種情況可以采用 auto 關鍵字作為返回值占位符。 decltype 操作符用於查詢表達式的數據類型,也是 C++11 標准引入的新的運算符,其目的是解決泛型編程中有些類型由模板參數決定,而難以表示的問題。為何要將返回值后置呢?
// 這樣是編譯不過去的,因為 decltype(a*b)中,a 和 b 還未聲明,編譯器不知道 a 和 b 是什么。
template
decltype(a*b) multi(T a, T b) {
return a*+ b;
}
//編譯時會產生如下錯誤:error: use of undeclared identifier 'a'
//template4.cpp
#include
using namespace std;
template
auto multi(T1 a, T2 b) -> decltype(a * b) {
return a * b;
}
int main(int argc, char* argv[])
{
cout << multi(2, 3) << endl;
cout << multi(2.2, 3.0) << endl;
cout << multi(2.2, 4) << endl;
cout << multi(3, 4.4) << endl;
return 0;
}

2.3 類成員函數模板
函數模板可以做為類的成員函數。
需要注意的是:函數模板不能用作虛函數。這是因為 C++編譯器在解析類的時候就要確定虛函數表(vtable)的大小,如果允許一個虛函數是函數模板,那么就需要在解析這個類之前掃描所有的代碼,找出這個模板成員函數的調用或顯式實例化操作,然后才能確定虛函數表的大小,而顯然這是不可行的。
//template5.cpp
#include <iostream>
class object {
public:
template<typename T>
void print(const char *name, const T &v){
std::cout << name << ": " << v << std::endl;
}
};
int main() {
object o;
o.print("name", "Crystal");
o.print("age", 18);
return 0;
}

2.4 函數模板重載
函數模板之間、普通函數和模板函數之間可以重載。編譯器會根據調用時提供的函數參數,調用能夠處理這一類型的最佳匹配版本。在匹配度上,一般按照如下順序考慮:

可以通過空模板實參列表來限定編譯器只匹配函數模板,比如 main 函數中的最后一條語句。
//template6.cpp
#include
#include
template
const T &max(const T &a, const T &b)
{ std::cout << "max(&, &) = ";
return a > b ? a : b;
}
// 函數模板重載
template
const T *max(T *a, T *b) {
std::cout << "max(*, *) = ";
return *a > *b ? a : b;
}
// 函數模板重載
template
const T &max(const T &a, const T &b, const T &c)
{ std::cout << "max(&, &, &) = ";
const T &t = (a > b ? a : b);
return t > c ? t : c;
}
// 普通函數
const char *max(const char *a, const char *b)
{ std::cout << "max(const char *, const char *) = ";
return strcmp(a, b) > 0 ? a : b;
}
int main() {
int a = 1, b = 2;
std::cout << max(a, b) << std::endl;
std::cout << *max(&a, &b) << std::endl;
std::cout << max(a, b, 3) << std::endl;
std::cout << max("en", "ch") << std::endl;
// 可以通過空模板實參列表來限定編譯器只匹配函數模板
std::cout << max<>("en", "ch") << std::endl;
std::cout << max(100, 200) << std::endl;
return 0;
}

2.5 函數模板特化 specialization
當函數模板需要對某些類型進行特別處理,這稱為函數模板的特化。當我們定義一個特化版本時,函數參數類型必須與一個先前聲明的模板中對應的類型匹配。函數模板特化的本質是實例化一個模板,而非重載它。因此,特化不影響編譯器函數匹配。
//template7.cpp
#include
#include
using namespace std;
template
int compare(const T1 &a, const T2 b) {
return a - b;
}
// 對 const char *進行特化
template<>
int compare(const char * const &a, const char * const &b) {
return strcmp(a, b);
}
int main(int argc, char* argv[])
{
cout << compare(100, 200) << endl;
cout << compare("abc", "xyz") << endl;
return 0;
}

上面的例子中針對 const char *的特化,我們其實可以通過函數重載達到相同效果。因此對於函數模板特化,目前公認的觀點是沒什么用,並且最好別用。Why Not Specialize Function Templates?
但函數模板特化和重載在重載決議時有些細微的差別。這些差別中比較有用的一個是阻止某些隱式轉換。如當你只有 void foo(int)時,以浮點類型調用會發生隱式轉換,這可以通過特化來阻止:
template <class T>void foo(T);
template <> void foo(int) {}
foo(3.0); // link error,阻止 float 隱式轉換為 int
雖然模板配重載也可以達到同樣的效果,但特化版的意圖更加明確。
函數模板及其特化版本應該聲明在同一個頭文件中。所有同名模板的聲明應該放在前面,然后是這些模板的特化版本。
程序運行結果和使用函數模板特化相同。但是,使用普通函數重載和使用模板特化還是有不同之處,主要表現在如下兩個方面:
(1)如果使用普通重載函數,那么不管是否發生實際的函數調用,都會在目標文件中生成該函數的二進制代碼。而如果使用模板的特化版本,除非發生函數調用,否則不會在目標文件中包含特化模板函數的二進制代碼。這符合函數模板的“惰性實例化”准則。
(2)如果使用普通重載函數,那么在分離編譯模式下,應該在各個源文件中包含重載函數的申明,否則在某些源文件中就會使用模板函數,而不是重載函數。
2.6 變參函數模板(模板參數包)
這是 C++11 引入的新特性,用來表示任意數量的模板形參。其語法樣式如下:
template<typename ...Args> // Args: 模板參數包
void foo(Args ... args);// args: 函數參數包
在模板形參 Args 的左邊出現三個英文點號"...",表示 Args 是零個或多個類型的列表,是一個模板參數包(template parameter pack)。正如其名稱一樣,編譯器會將 Args 所表示的類型列表打成一個包,將其當做一個特殊類型處理。相應的函數參數列表中也有一個函數參數包。與普通模板函數一樣,編譯器從函數的實參推斷模板參數類型,與此同時還會推斷包中參數的數量。
// sizeof...() 是 C++11 引入的參數包的操作函數,用來取參數的數量
template
int length(Args ... args) {
return sizeof...(Args);
}
// 以下語句將在屏幕打印出:2
std::cout << length(1, "hello") << std::endl;
變參函數模板主要用來處理既不知道要處理的實參的數目也不知道它們的類型時的場景。既然我們對實參數量以及類型都一無所知,那么我們怎么使用它呢?最常用的方法是遞歸。
//template8.cpp
#include
using namespace std;
// sizeof...() 是 C++11 引入的參數包的操作函數,用來取參數的數量
template
int length(Args ... args) {
return sizeof...(Args);
}
int main(int argc, char* argv[])
{
// 以下語句將在屏幕打印出:2
cout << length(1, "hello") << endl; // 以下語句將在屏幕打印出:3
cout << length(1, "hello", 2) << endl; // 以下語句將在屏幕打印出:4
cout << length(1, "hello", 2, 3) << endl; // 以下語句將在屏幕打印出:5
cout << length(1, "hello", 2, 3, 4) << endl;
return 0;
}
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <vector> #include <map> // 在C++11之前,類模板和函數模板只能含有固定數量的模板參數。C++11增強了模板功能,允許模板定義中包含0到任意個模板參數,這就是可變參數模板。 // 可變參數模板類 繼承方式展開參數包 // 可變參數模板類的展開一般需要定義2 ~ 3個類,包含類聲明和特化的模板類 template<typename... A> class BMW{}; // 變長模板的聲明 template<typename Head, typename... Tail> // 遞歸的偏特化定義 class BMW<Head, Tail...> : public BMW<Tail...> {//當實例化對象時,則會引起基類的遞歸構造 public: BMW() { printf("type: %s\n", typeid(Head).name()); } Head head; }; template<> class BMW<>{}; // 邊界條件 // 模板遞歸和特化方式展開參數包 template <long... nums> struct Multiply;// 變長模板的聲明 template <long first, long... last> struct Multiply<first, last...> // 變長模板類 { static const long val = first * Multiply<last...>::val; }; template<> struct Multiply<> // 邊界條件 { static const long val = 1; }; void mytest() { BMW<int, char, float> car; /* 運行結果: type: f type: c type: i */ std::cout << Multiply<2, 3, 4, 5>::val << std::endl; // 120 return; } int main() { mytest(); system("pause"); return 0; }
類模板 https://blog.csdn.net/qq_35637562/article/details/55194097
考慮我們寫一個簡單的棧的類,這個棧可以支持int類型,long類型,string類型等等,不利用類模板,我們就要寫三個以上的stack類,其中代碼基本一樣,通過類模板,我們可以定義一個簡單的棧模板,再根據需要實例化為int棧,long棧,string棧。
statck.h template <class T> class Stack { public: Stack(); ~Stack(); void push(T t); T pop(); bool isEmpty(); private: T *m_pT; int m_maxSize; int m_size; }; #include "stack.cpp"
//stack.cpp template <class T> Stack<T>::Stack(){ m_maxSize = 100; m_size = 0; m_pT = new T[m_maxSize]; } template <class T> Stack<T>::~Stack() { delete [] m_pT ; } template <class T> void Stack<T>::push(T t) { m_size++; m_pT[m_size - 1] = t; } template <class T> T Stack<T>::pop() { T t = m_pT[m_size - 1]; m_size--; return t; } template <class T> bool Stack<T>::isEmpty() { return m_size == 0; }
模板參數
模板可以有類型參數,也可以有常規的類型參數int,也可以有默認模板參數,例如
template<class T, T def_val> class Stack{...}
上述類模板的棧有一個限制,就是最多只能支持100個元素,我們可以使用模板參數配置這個棧的最大元素數,如果不配置,就設置默認最大值為100