1、简单模板函数
先看一个简单的模板函数max,它接受两个类型为T的参数,并且返回类型为T的结果,用它可以比较两个整数、浮点数,或者是支持>
运算符的类型之间的最大值:
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
下面是如何使用这个模板的例子:
void TestSimpleTemplete() {
std::cout << MyTemplate::max<int>(10, 20) << std::endl;
std::cout << MyTemplate::max<double>(3.14, 5.22) << std::endl;
std::cout << MyTemplate::max<char>('a', 'b') << std::endl;
class InnerData {
public:
InnerData(int x) :val(x) {}
bool operator>(InnerData &other) {
return this->val > other.val;
}
int getVal() { return val; }
private:
int val;
};
std::cout << MyTemplate::max<InnerData>(InnerData(2), InnerData(3)).getVal() << std::endl;
}
// output
// 20
// 5.22
// b
// 3
2、显式实例化与隐式实例化
接下来我们要引出实例化的概念,实例化指的是编译器根据给定的模板参数创建函数或者类型的过程,简单来说就是编译器确认模板类型。
以上述max函数为例,我们使用时指定模板参数为int,就是MyTemplate::max<int>
模板实例化过程,我们人为/主动指定参数为int的动作被称为显式实例化。
对应的还有隐式实例化,我们不需要传递具体类型,编译器编译时会为我们自动推断模板参数的类型。
使用隐式实例化同样可以获得预期的结果:
void TestSimpleTemplete() {
std::cout << MyTemplate::max(10, 20) << std::endl;
std::cout << MyTemplate::max(3.14, 5.22) << std::endl;
std::cout << MyTemplate::max('a', 'b') << std::endl;
std::cout << MyTemplate::max(InnerData(2), InnerData(3)).getVal() << std::endl;
}
// output
// 20
// 5.22
// b
// 3
什么时候用显式实例化,什么时候用隐式实例化呢?
使用隐式实例化时,我们希望模板参数类型可以直接通过上下文来推导,这是比较推荐的方式;如果编译器无法自动推导出模板参数类型,或者是我们希望显式地指定一个不同的类型,这时候我们要用显式实例化。接下来我们举例来说明:
如果我们想用max比较一个int和一个double类型,用隐式实例化的方法去写,在编译时会产生error,提示没有与参数列表匹配的函数模板,实例参数类型为(int, double)
// error
std::cout << MyTemplate::max(10, 2.1) << std::endl; // error example
但是我们目的就是想要比较这两个值要怎么做呢?我们需要显式实例化,指定模板类型:
std::cout << MyTemplate::max<int>(1, 2.1) << std::endl; // 2
std::cout << MyTemplate::max<double>(1, 2.1) << std::endl; // 2.1
要注意的是,指定的类型不同返回的结果也会不同。
另一种情况,在参数为2个double时,用隐式实例化编译器会帮自动推导类型为double,但是我们偏要使用int来处理,这时候就要用显式实例化。
std::cout << MyTemplate::max<int>(1.1, 2.1) << std::endl; // 2
从上面的例子中我们可以了解到,一般来说使用隐式实例化让模板自动推导即可;如果编译器无法推导,或者是希望覆盖自动推导结果时,我们可以使用显式实例化。
注意!隐式/显式实例化针对的是模板函数,模板类并没有隐式实例化的说法!
3、简单模板类
以下是一个简单的模板类,示例中展现了模板类方法的类内以及类外两种实现方式:
template <typename T>
class Box {
public:
Box() :pointer(0) {}
void push(T elem);
void pop() {
if (pointer > 0) {
std::cout << "pop [" << arr[pointer-1] << "] ok" << std::endl;
--pointer;
} else {
std::cout << "pop fail, no element" << std::endl;
}
}
private:
T arr[100];
int pointer;
};
template <typename T>
void Box<T>::push(T elem) {
if (pointer <= 99) {
std::cout << "push [" << elem << "] ok" << std::endl;
arr[pointer] = elem;
++pointer;
} else {
std::cout << "push fail, no space" << std::endl;
}
}
模板类和模板函数的使用方法类似,不同的是模板类没有隐式实例化的说法,实例化时一定要显式指定类型参数:
void TestSimpleTempleteClass() {
MyTemplate::Box<int> ibox;
ibox.push(1);
ibox.push(2);
ibox.pop();
MyTemplate::Box<double> dbox;
dbox.push(1.1);
dbox.push(2.2);
dbox.pop();
dbox.pop();
}
普通类的方法实现一般会放到cpp文件当中,但是模板类的方法实现必须要放在头文件当中。这是因为模板不是普通代码,它们是编译器用来生成特点类型代码的一种工具,编译器需要看到完整的模板定义才能在使用模板的源文件中生成对应的代码实例。
如果把模板类的实现放在cpp文件中,那么其他cpp文件会无法看到这些实现代码,编译器也就无法生成对应的类实例。