5 Templates¶
Same function name with different argument-lists.
void print(char * str, int width); // #1
void print(double d, int width); // #2
void print(long l, int width); // #3
void print(int i, int width); // #4
void print(char *str); // #5
print("Pancakes", 15);
print("Syrup");
print(1999.0, 10);
print(1999, 12);
print(1999L, 15);
为了使 print
函数可以处理不同的参数,我们需要为每一个类型写一个函数,这造成了代码的重复,也不方便后期维护。为此我们引入模板的概念。
我们可以定义函数模板或者类模板,接下来将详细阐述
Function templates
Similar operations on different types of data.
以 swap
函数为例
-
The
template
keyword introduces the template -
The class
T
specifies a parameterized type name (class means any built-in type or UDT) - Inside the template, use
T
as a type name
类型参数 T
可以作为传入函数的参数的类型,返回值的类型,函数内局部变量的类型。编译器会根据模板参数生成一份相应的函数,并对其进行语法检查,这个函数只有在实例化的时候才会出现。
int i = 3; int j = 4;
swap(i, j); // use explicit int swap
float k = 4.5; float m = 3.7;
swap(k, m); // instantiate float swap
std::string s("Hello");
std::string t("World");
swap(s, t); // instantiate std::string swap
模板也可以特化,函数模板只能完全特化
emplate <typename T>
void func(T param) {
// 通用实现
}
// 完全特化:针对int类型的特化版本
template <>
void func<int>(int param) {
// int类型的特殊实现
}
模板函数进行推导的时候不会对参数进行隐式转换,只会匹配相应的版本
模板函数也可以显示地推导
template <class T>
void foo() { /* ... */ }
foo<int>(); // type T is int
foo<float>(); // type T is float
然后模板函数和普通函数是可以共存的,调用顺序如下
-
First, check for exact regular-function match
-
Then, check for exact function-template match
-
Last, implicit conversions for regular functions
void f(float i, float k) { /*...*/ };
template <class T> void f(T t, T u) { /*...*/ };
f(1.0f, 2.0f); //调用的是第一个函数
f(1.0, 2.0);//调用的是第二个函数
f(1, 2);//这里调用的也是第二个函数
f(1, 2.0);//这里调用的是第一个函数,做了隐式转换
Class templates
类模板使用类型参数来定义类,可以为同一个类结构指定不同的数据类型,最经典的用处为容器的类模板,比如说 stack
,list
,queue
等等,下面是 Vector
容器的实现
template <class T>
class Vector {
public:
Vector(int);
~Vector();
Vector(const Vector&);
Vector& operator=(const Vector&);
T& operator[](int);
private:
T* m_elements;
int m_size;
}
使用方法就如下
相应的成员函数如下
template <class T>
Vector<T>::Vector(int size): m_size(size) {
m_elements = new T[m_size];
}
template <class T>
T& Vector<T>::operator[](int index) {
if(index < m_size && index >= 0) {
return m_elements[index];
} else {
/*...*/
}
}
类模板的特化
template <typename T, typename U>
class MyTemplate {
public:
void print(T t, U u) {
std::cout << "Generic: " << t << ", " << u << std::endl;
}
};
下面是一个完全特化
template <>
class MyTemplate<int, double> {
public:
void print(int i, double d) {
std::cout << "Specialized int and double: " << i << ", " << d << std::endl;
}
};
下面是一个部分特化
template <typename T>
class MyTemplate<T, double> {
public:
void print(T t, double d) {
std::cout << "Partially specialized with double: " << t << ", " << d << std::endl;
}
};
template <typename T>
class MyTemplate<T, T*> {
public:
void print(T t, T* d) {
std::cout << "Partially specialized : " << t << ", " << d << std::endl;
}
};
模板可以拥有多个类型参数,比如说下面这个类模板
template < class Key, class Value >
class HashTable {
const Value& lookup (const Key&) const;
void insert (const Key&, const Value&);
/* ... */
}
模板也可以被嵌套,毕竟一个模板加上类型参数就可以被认为是一个新的类型了
Expression parameters:模板中还可以有非类型参数,比如说
template <class T, int bounds = 100>
class FixedVector {
public:
FixedVector();
T& operator[](int);
private:
T elements[bounds]; // fixed-size array!
}
使用方法如下
FixedVector<int, 50> v1;
FixedVector<int, 10*5> v2;
FixedVector<int> v3; // => FixedVector<int, 100>
Member templates
Template declarations can appear inside a memberspecification of any class
比如说下面复数类中的构造函数
template<typename T> class complex
{
public:
template<class X> complex(const complex<X>&);
/* ... */
};
使用场景如下
complex<double> c1(3.0, 4.0);
complex<float> c2(5.0f, 6.0f);
// 使用模板构造函数将 c2 转换为 complex<double>
complex<double> c3(c2); // 此处调用了 template<class X> complex(const complex<X>&) 构造函数
关于模板类的继承,可以继承于一个非模板类,也可以继承于一个模板类。非模板类也可以继承于一个实例化的模板类
template <class A>
class Derived : public Base { /* ... */ }
template <class A>
class Derived : public List<A> { /* ... */ }
class SupervisorGroup : public
List<Employee*> { /* ... */ }
CRTP (The Curiously Recurring Template Pattern)
一种特殊的模板使用方式,也是一种实现多态的方式。与传统的通过虚函数实现的动态多态不同,CRTP可以在编译期解决派生类的具体类型,从而提高运行时效率。
基本的框架如下,基类为模板类,派生类将自身作为模板参数给到基类
下面是一个利用 CRTP 实现多态的例子
#include <iostream>
// 定义基类模板
template <typename Derived>
class Base {
public:
void interface() {
// 调用实现于派生类中的方法
static_cast<Derived*>(this)->implementation();
}
static void static_interface() {
Derived t;
t.implementation();
}
};
// 定义第一个派生类
class Derived1 : public Base<Derived1> {
friend class Base<Derived1>; // 确保Base可以访问Derived1的私有成员
private:
void implementation() {
std::cout << "Derived1 implementation" << std::endl;
}
};
// 定义第二个派生类
class Derived2 : public Base<Derived2> {
friend class Base<Derived2>; // 确保Base可以访问Derived2的私有成员
private:
void implementation() {
std::cout << "Derived2 implementation" << std::endl;
}
};
int main() {
Derived1 d1;
Derived2 d2;
// 通过基类接口调用派生类的方法
Base<Derived1>& b1 = d1;
b1.interface(); // 输出: Derived1 implementation
Base<Derived2>& b2 = d2;
b2.interface(); // 输出: Derived2 implementation
// 使用静态接口
Base<Derived1>::static_interface(); // 输出: Derived1 implementation
Base<Derived2>::static_interface(); // 输出: Derived2 implementation
return 0;
}
Morality(一些规范):Put the definition/declaration for templates in the header file
-
won 't allocate storage for the function/class at that point 不会立刻为这个函数或者类分配空间,在实例化的时候分配
-
compiler/linker have mechanisms for removing multiple definitions 编译器或者连接器有一些机制可以移除重复定义