模板是 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);