模板(类模板或函数模板)和类或函数很相似。当你实例化一个模板,你就创建了一个具体的类或函数。出于习惯,有时也将类模板称为泛型类,将函数模板成为泛型函数。
为了可视化模板实例化的过程,可以使用C++ Insights。
什么时候使用模板
你应该使用模板当函数或类代表了一个抽象的概念,并且不局限于某个特定的类型。例如:函数max,容器vector都能用于多种类型。
如何创建模板
假如有如下函数:
int max(int lhs, int rhs) {
return (lhs > rhs)? lhs : rhs;
}
将其变为一个模板很简单:
- 在函数前面加上
template <typename T>
- 使用参数T替换具体的类型int。
template <typename T> // typename 也可以使用 class 代替
T max(T lhs, T rhs) {
return (lhs > rhs)? lhs : rhs;
}
当实例化模板时会发生什么?
使用int
和double
实例化函数模板max:
template <typename T>
T max(T lhs, T rhs) {
return (lhs > rhs)? lhs : rhs;
}
int main() {
max(10, 5);
max(10.5, 5.5);
}
C++ Insights显示了模板实例化的具体过程:
template <typename T>
T max(T lhs, T rhs) {
return (lhs > rhs)? lhs : rhs;
}
/* First instantiated from: insights.cpp:8 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
int max<int>(int lhs, int rhs)
{
return (lhs > rhs) ? lhs : rhs;
}
#endif
/* First instantiated from: insights.cpp:9 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
double max<double>(double lhs, double rhs)
{
return (lhs > rhs) ? lhs : rhs;
}
#endif
int main()
{
max(10, 5);
max(10.5, 5.5);
return 0;
}
可以看到编译器分别为int
和double
生成了特化的函数。
当使用相同类型实例化模板多次时,会发生什么?
下面是一个类模板的例子。
template <typename T, int N>
class Array{
public:
int getSize() const{
return N;
}
private:
T elem[N];
};
int main() {
Array<int, 5> myArr1; // (1)
Array<int, 10> myArr2; // (2)
Array<int, 5> myArr3; // (3)
}
Array<int, 5>
实例化了2次,Array<int, 10> (line 2)
实例化了一次。C++ Insights的主要输出如下:
template <typename T, int N>
class Array{
public:
int getSize() const{
return N;
}
private:
T elem[N];
};
/* First instantiated from: insights.cpp:13 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class Array<int, 5>
{
public:
inline int getSize() const;
private:
int elem[5];
public:
// inline Array() noexcept = default;
};
#endif
/* First instantiated from: insights.cpp:14 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class Array<int, 10>
{
public:
inline int getSize() const;
private:
int elem[10];
public:
// inline Array() noexcept = default;
};
#endif
可以看到,第二次实例化(3)Array<int, 5>
使用了第一次实例化的代码。
有2个有趣的发现:1. 模板实例化是延迟(lazy)的; 2. 可以使用非类型作为模板参数。
延迟实例化
从上面输出可以看到成员函数getSize()
没有实例化,仅有声明。这个意味着如果没有使用将不会被实例化。
修改代码如下:
int main() {
Array<int, 5> myArr1;
Array<int, 10> myArr2;
Array<int, 5> myArr3;
myArr3.getSize(); // (1)
}
编译器将会生成getSize
的代码:
...
/* First instantiated from: insights.cpp:13 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class Array<int, 5>
{
public:
inline int getSize() const
{
return 5;
}
private:
int elem[5];
public:
// inline Array() noexcept = default;
};
#endif
...