阅读经典——《C++ Templates》01
- 函数模板
- 类模板
- 非类型模板参数
- 一些技巧
- 模板代码的组织结构
一、函数模板
定义:
template <typename T>
inline T const& max (T const& a, T const& b)
{
return a < b ? b : a;
}
使用:
max(7, 1);
max(7.1, 1.2);
max("mathematics", "math");
编译时,函数模板根据实参来确定模板参数T的类型,针对每一种类型实例化出不同的函数。由于max
中使用了比较运算符operator<
,因此类型T
必须支持该操作,否则编译器报错。
模板参数不支持自动类型转换,例如,下面的调用会出错。
max(7, 1.2); //wrong
编译器无法根据实参决定T
的类型,因为7
和1.2
是两种不同的类型,这里不允许int
自动转换为double
。有一种妥协方案,显式指定T
的类型,这时int
可以转换为double
。
max<double>(7, 1.2);
找错误:
下面的程序隐藏着一个惊天Bug,请把它找出来。
template <typename T1, typename T2>
inline T1 const& max (T1 const& a, T2 const& b)
{
return a < b ? b : a;
}
max(4, 4.2);
答案见文末。
二、类模板
声明和定义:
template <typename T>
class Stack {
private:
std::vector<T> elems;
public:
Stack();
void push(T const&);
void pop();
T top() const;
};
template <typename T>
void Stack<T>::push (T const& elem)
{
elems.push_back(elem);
}
...
使用:
Stack<int> intStack;
Stack<std::string> stringStack;
intStack.push(7);
stringStack.push("hello");
编译时,类模板根据模板参数实例化出相应的类对象和成员变量,而成员函数并不一定实例化,只有那些被调用了的成员函数才会被实例化。显然这样做可以节省空间,而且,对于那些“未能支持所有成员函数中的所有操作”的类型,只要不调用那些不支持的成员函数,就仍然可以使用。听起来有些抽象,举个例子,假如Stack
中也有max
操作,那么如果使用自定义类型Person
作为Stack
的模板参数,而且Person
没有重载operator<
运算符,那么该stack
对象就不能访问max
方法,若访问则编译器会报错。
局部特化:
可以指定类模板的特定实现,并且要求某些模板参数仍然必须由用户来定义。
例如类模板:
template <typename T1, typename T2>
class MyClass {
...
};
就可以有下面几种局部特化:
//两个模板参数具有相同的类型
template <typename T>
class MyClass<T, T> {
...
};
//第2个模板参数的类型是int
template <typename T>
class MyClass<T, int> {
...
};
//两个模板参数都是指针类型
template <typename T1, typename T2>
class MyClass<T1*, T2*> {
...
};
缺省模板实参:
可以为模板参数定义缺省值。例如,在Stack<>
类中把用于存放元素的容器类型定义为第2个模板参数,并使用std::vector<>
作为缺省值。
template <typename T, typename CONT = std::vector<T> >
class Stack {
private:
CONT elems;
...
};
三、非类型模板参数
模板参数并不一定是属于typename
的类型,也可以是普通值,称为非类型模板参数。例如,我们可以把栈容量MAXSIZE
作为Stack<>
的一个非类型模板参数,并用它初始化数组大小。
template <typename T, int MAXSIZE>
class Stack {
private:
T elems[MAXSIZE];
...
};
使用方式如下:
Stack<int, 20> int20Stack;
Stack<int, 40> int40Stack;
Stack<std::string, 40> stringStack;
非类型模板参数只能是常整数(包括枚举)或指向外部链接对象的指针。(关于外部链接对象的概念请参考...)
四、一些技巧
关键字typename:
typename最初用于指定模板内部的标识符是一个类型,例如:
template <typename T>
class MyClass {
typename T::SubType* ptr;
...
};
如果不加typename
,SubType
会被认为是T
的一个静态成员,而不会被认为是一个内部类型。
成员模板:
如果类的成员函数也是独立的模板函数,则称之为成员模板。例如,给Stack<>
类增加一个赋值操作符operator=
成员模板函数。
//声明
template <typename T>
class Stack {
...
public:
...
template <typename T2>
Stack<T>& operator= (Stack<T2> const&);
};
//定义
...
template <typename T>
template <typename T2>
Stack<T>& Stack<T>::operator= (Stack<T2> const& op2)
{
...
}
模板的模板参数:
当模板参数也是一个模板的时候,情况就变得复杂了。例如,Stack<T, CONT>
中,模板参数CONT
就是一个模板,我们需要传入std::vector<T>
作为实参。但是这样做无法约束vector
的模板参数T
与Stack
的第一个模板参数T
一致,有可能出错。这种情况下使用模板的模板参数更合适。
template <typename T, template <typename ELEM> class CONT = std::deque>
class Stack {
private:
CONT<T> elems;
...
};
使用时不必传入容器类的模板参数,它会自动根据Stack
类的模板参数决定。
Stack<int> intStack; //使用缺省模板参数
Stack<float, std::vector> floatStack; //使用vector<float>作为容器
模板的模板参数只能使用class作为关键字,因为只有类可以作为模板的模板参数。函数模板不支持模板的模板参数。
零初始化:
未初始化的基本数据类型通常具有一个不确定(undefined)值。因此建议采用如下写法:
template <typename T>
void foo()
{
T x; //不建议这样写,如果T是基本数据类型,那么x本身是一个不确定的值
T x = T(); //建议这样写,如果T是基本数据类型,那么x是0或者false
}
五、模板代码的组织结构
我们通常把声明写在.h文件中,把定义写在.cpp文件中。然而这种惯例被模板打破了。
如果把函数模板的声明和定义分别写在两个文件中,链接器将会报错,提示找不到函数的定义。这是因为,函数模板还没有实例化,也就是说,函数模板的定义所在的文件并没有被编译,因为编译器不知道应该使用哪个模板参数来实例化。因此,通常的做法是,把函数模板的声明和定义全部放在头文件中。
//myfirst2.h
#ifndef MYFIRST_H
#define MYFIRST_H
#include <iostream>
#include <typeinfo>
//模板声明
template <typename T>
void print_typeof(T const&);
//模板的实现/定义
template <typename T>
void print_typeof(T const& x)
{
std::cout << typeid(x).name() << std::endl;
}
#endif
模板代码的这种组织结构称为包含模型。除此之外,还有显式实例化、分离模型等组织结构,但都不常用,特别是分离模型(使用export关键字导出模板)已经被c++标准委员会废除。
找错误答案
template <typename T1, typename T2>
inline T1 const& max (T1 const& a, T2 const& b)
{
return a < b ? b : a;
}
max(4, 4.2);
T1
为int
,T2
为double
,返回值类型为int
。由于需要返回的数是double
类型的b
,因此需要一个从double
到int
的转换,这次转换将会创建一个临时int
型变量作为返回值。而由于返回类型为引用,导致该函数返回后将持有一个临时局部变量的引用,一旦临时变量被释放,继续使用这个引用将得到意想不到的结果,甚至引起程序崩溃。
解决方案是返回值不使用引用。这个问题很容易出现,与此类似的还有返回局部变量的指针,因此编程时需要多加注意。
关注作者或文集《C++ Templates》,第一时间获取最新发布文章。