模板是 C++ 泛型的一種強大特性,可以應用於類、函數、變量,模板的存在使得 coder 可以編寫類型無關的代碼,而在編譯時生成一般的類、函數和變量。
模板基本形式#
模板定義了一種藍圖,以生成任何可以操作任何類型的數據的類和函數:
模板的基本形式#
以常見的函數模板為例,一個模板的定義如下:
template <typename T>
T func(T param){
// do something...
return param;
}
上述代碼是一個具有單個類型參數 T 的函數模板,其參數、返回值都是 T 類型。
- 關鍵詞
template
指明定義模板 - 關鍵詞
typename
是參數類型的佔位符,T
作為模板參數,編譯時,編譯器根據函數的調用將T
的實例的類型替換為具體的參數類型,這個過程即為 “模板實例化”。
在調用時,以正常函數的方式調用:
int i = func(42);
類模板#
template <typename T>
class Array {
private:
T* data;
size_t size;
public:
explicit Array(size_t size): size(size), data(new T[size]) {}
~Array() {delete[] data;}
T& operator[](size_t index) {return data[index];}
}
函數模板#
template <typename T>
T max(T a, T b) {
return (a > b)? a:b;
}
變量模板#
變量模板是 C++14 引入的新特性,可以生成任何類型的靜態變量:
template <typename T>
constexpr T pi = T(3.1415)
模板的類型參數#
模板對參數數量不限制,這與普通函數一致:
template <typename T, typename U, typename V>
可以定義任意數量的類型參數的模板:
template<typename... Args> class vtclass;
template<typename... Args>
void func(Arguments... args)
在 C++17 後,可以使用自動推導的類型,當auto
用在模板參數位置時,它允許模板接受任何類型的非類型模板參數(non-type template parameter),這包括但不限於整數、字符和布爾值。這樣的模板被稱為自動類型推導的非類型模板參數(auto-typed non-type template parameters):
template <auto x> constexpr auto constant = x;
模板參數<auto x>
表示constant
可以接受任何類型的常量表達式作為參數。constant
的值將會是傳給它的參數x
的值。
默認模板變量#
默認模板變量的聲明和普通函數一致:
template <typename T = int>
void func(T factor){
// do something
}
func<>(42);
模板的特化#
模板提供了很強大的泛型能力,但我們在定義一個模板時,不會為每一種 case 都編寫重複的代碼,很多情況下,我們都希望的是僅當參數類型在某些情況下有著不同的執行路徑,這時候需要用到模板特化。
template <typename T, typename U>
class MyClass{}; // 定義模板
template <typename T>
class MyClass<T, int>{} // 模板特化
MyClass<int, string> myClass1; // 原始的模板
MyClass<int, int> myClass2; // 模板特化
類模板#
類模板的成員函數#
可以在類模板中定義函數,也可以在類的外部進行定義,當模板成員函數在類的外部定義時,需要前置類模板的模板聲明部分來指定。
template<class T, int i>
class MyStack {
// 類成員和方法聲明
};
template<class T, int i>
void MyStack<T, i>::push(const T item) {
// do something
}
template<class T, int i>
T& MyStack<T, i>::pop() {
// do something
}
更常見的做法是在類模板中直接定義模板成員函數,這種寫法有助於編譯器進行內聯處理,提高效率,同樣的也能簡化代碼。
對於一般的非模板的函數或者類,通常的做法是在頭文件中聲明,在源文件中實現,但在模板編程時,為了保證模板在編譯時能夠訪問到完整的模板定義,把模板的定義和聲明都直接放在頭文件中反而是最佳的實踐,否則,除非為每一種類型使用的模板手動實例化,否則在進行鏈接時可能因為找不到模板實例的定義出現鏈接錯誤。
對於複雜大型的模板庫,經常會把模板的實現代碼放在
.ipp
或者.tpp
文件中,然後在頭文件底部包含:
// MyTemplate.h
template<typename T>
class MyTemplate {
public:
void func(T value);
};
#include "MyTemplate.tpp"
// MyTemplate.tpp, 實際包含模板的實現
template<typename T>
void MyTemplate<T>::doSomething(T value) {
// 方法實現
}
嵌套類模板#
模板可以進行嵌套:
#include <iostream>
template<class T>
class A{
public:
template <class U>
class B{
private:
U u; // 使用U類型的值而不是指針,簡化示例
public:
B() {} // 構造函數實現
U& Value() { return u; } // 返回U類型的引用
void print() { std::cout << u << std::endl; } // 打印U類型的值
~B() {} // 析構函數實現
};
B<int> b;
public:
A(T t) { b.Value() = t; }
void print() { b.print(); } // 調用B<int>的print
};
// 在外部定義嵌套類模板成員函數時的語法示例
template<class T>
template<class U>
A<T>::B<U>::B() {
// 構造函數具體實現
}
template<class T>
template<class U>
U& A<T>::B<U>::Value() {
// 這個函數可以返回U類型的引用,此處只是一個示例
return u;
}
int main() {
A<int> a(42); // 創建A類的實例
a.print(); // 打印42
}
函數模板#
函數模板實例化#
可以使用顯式指定或推導的方式對函數進行實例化:
template<class T> void f(T) { }
template void f<int> (int);
template void f(char);
可以使用extern
關鍵字來避免重複的代碼生成(實例化)達到對代碼進行精細控制的目的。
template<typename T, std::size_t N>
class MyClass {};
exterm template MyClass<int, 4>::MyClass(void);