1. 定义模板
有这样的一个需求,定义一个比较两个数大小的函数,但是这两个数的类型是不确定的,我们现在能想到的实现方法是定义多个重载的函数:
// 字符串版本的
int compare(const string &v1, const string &v2) {
if (v1 < v2) return -1;
if (v1 > v2) retrurn 1;
return 0;
}
// 浮点类型
int compare(const double &v1, const double &v2) {
if (v1 < v2) return -1;
if (v1 > v2) retrurn 1;
return 0;
}
以上这两个函数唯一的区别就是参数类型的不同,函数内部的实现完全一致。实际的应用场景中可能还不只上面两种类型,可能更多,很显然重载函数的方法是不可行的。这就要使用到本章的模板编程。
1.1 函数模板
针对以上的需求我们可以使用函数模板来实现,模板具体的代码如下
template<tyoename T>
int compare(const T &v1, const T &v2) {
if (v1 < v2) return -1;
if (v1 > v2) retrurn 1;
return 0;
}
模板的定义以关键字template
开始,后跟一个模板参数列表
在模板定义中,模板参数列表不能为空
模板参数列表的作用类似于函数的参数列表,模板是把类型参数化。
实例化函数模板
当我们调用 compare
时编译器会实例化一个特定的函数,并且会自动的推导模板的类型参数
cout << compare(1, 0) << endl;
// 显示指定类型
cout << compare<int>(1, 0) << endl;
这里 T
模板被推导为 int
类型,即实例化的函数是:
int compare(const int& , const int&);
模板类型参数
模板函数的类型参数可以看作类型说明符,可以在模板函数的内部使用:
template<typename T>
T foo(T* p) {
T tmp = *p;
// ...
return tmp;
}
模板列表可以有多个类型参数,用逗号分割开,但是每个类型参数前都需要加 typename
或class
关键字
template<typename T, U> // 错误
T calc(const T&, const U&);
// 正确
template<typename T, class U>
T calc(const T&, const U&);
typename
和 class
在定义模板参数时,两者时完全等价的,没有任何区别。typename
是 C++11添加的,而 class
是之前标准中使用的,为了保持兼容性,class
也一直保留着。
非类型模板参数
除了类型参数,还可以在模板中定义非类型参数。一个非类型参数表示的是一个常量值
template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M]) {
return strcmp(p1, p2);
}
// 调用
compare("hi", "mmo");
编译器会使用字面常量的大小来代替 N
和 M
,从而实例化模板。上面调用生成的函数实例:
// 字符串结尾有一个结束符
int compare(const char (&p1)[3], const char (&p2)[4]);
非类型模板参数的模板实参必须是常量表达式
inline 和 constexpr 的函数模板
函数模板可以声明为 inline
或 constexpr
, 这两个说明符放在模板参数列表之后,返回类型之前:
// 正确
template<typename T> inline T min(const T&, const T&);
// 错误
inline template<typename T> T min(const T&, const T&);
模板编译
当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。
通常,我们调用一个函数时,编译器只需要知道声明。类似的,使用一个类类型的对象时,类定义必须是可用的,但成员函数的定义不必已经出现。因此函数声明和或类定义放在头文中,而函数定义和类成员函数的定义放在源文件中。
模板则不同:为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,==模板的头文件通常既包括声明也包括定义。==
1.2 类模板
类模板 是用于生成类的蓝图。与函数模板的不同之处是,==编译器类模板不能为类模板推断模板参数类型。==
定义类模板
以 StrBlob
为例,实现它的模板版本Blob
template<typename T>
class Blob {
public:
typedef T value_type;
typedef typename std::vector<T>::size_type size_type;
//构造函数
Blob();
Blob(std::initializer_list<T> il);
size_type size() const {return data->size; }
bool empty() const { return data->empty(); }
void push_back(T&& t) { data->push_back(std::move(t)); }
void pop_back();
T& back();
T& operator[](size_type i);
private:
std::shared_ptr<std::vector<T>> data;
void check(size_type i, const std::string &msg) const;
};
上面代码和12.1.1 小节的代码差不多,只是用 T
替换了 string
实例化类模板
模板的实例化需要显式的指定模板参数的类型,编译器不能推导
Blob<int> ia; // 空 Blod<int>
Blob<int> ia={0, 1, 2, 3, 4}; // 有5个参数的 Blod<int>
对我们指定的每一种元素类型,编译器都生成一个不同的类:
Blod<string> names;
Blod<bouble> prices;
上面创建了两个不类型的实例。编译器会生成两个==完全独立==的类。
在模板作用域引用模板类型
一个实例化的类型总是包含类型参数, 但是如给在类模板种使用了另一个模板,我们可以使用一个模板参数传递给另一个模板:
std::shared_ptr<std::vector<T>> data;
在 Blod
中 使用了类模板vector
,我们就是使用的Blod
的模板参数T
传递给 vector
的
类模板的成员函数
和普通的类成员函数一样,我们既可以在类内定义成员函数,也可以在在类外定义。但是需要注意在类外定义的格式:
template<typename T>
ret-type Blod<T>::func-name(parm-list) { /* */ }
// back()的定义
template<typename T>
T& Blod<T>::back() {}
需要注意无论是函数模板还是类模板,声明和定义通常写在同一个文件(头文中)
check 和元素访问成员
check
的定义
template<typename T>
void Blob<T>::check(Blob::size_type i, const std::string& msg) const {
if (i >= data->size()) {
throw std::out_of_range(msg);
}
}
其他成员函数的定义
template<typename T>
T& Blob<T>::back() {
check(0, "back on empty Blod");
return data->back();
}
template<typename T>
T& Blob<T>::operator[](size_type i) {
check(i, "subscript out of range");
return (*data)[i];
}
template<typename T>
void Blob<T>::pop_back() {
check(0, "pop_back on empty Blod");
data->pop_back();
}
类模板成员函数的实例化
==默认情况下,一个类模板的成员函数只有当程序用到它时才进行实例化==
// 实例化 Blod<int> 和 Blob(std::initializer_list<T> il);
Blod<int> squares = {0, 1, 2, 3, 4, 5};
其他没有被调用的成员函数都没有被实例化。
在类代码内简化模板类名的使用
模板类的使用必须跟上模板参数,但是有一个例外,即在自己的作用域内(类内),我们可以直接使用模板名而不提供模板实参
template<typename T>
class BlobPtr {
public:
// ...
BlobPtr& operator++(); // 前置++
};
上面的返回类型直接使用模板名称,而没有指定模板参数。如果是在类外定义还是需要指定模板实参的
template<typename T>
BlobPtr<T>& BlobPtr<T>::operator++() {
//...
}