什么是模板
模板是泛型编程的基础, 是代码复用的模具.
模板的实现原理
编译器根据传入的实参类型推演生成对应类型的函数和类, 也就是说在传入实参类型之前, 模板是未实例化的, 模板只有实例化之后才会是真正的函数或类, 这也是模板不能很好的支持分离编译的原因.
模板
函数模板:
#include <iostream>
using namespace std;
template<class T> // template<typename T>
void Swap(T& a, T& b) {
T tmp = a;
a = b;
b = tmp;
}
int main() {
int a = 1;
int b = 2;
Swap(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
return 0;
}
函数模板相比于函数重载的优点:
- 函数重载只针对参数列表的不同, 而模板与类型无关, 代码复用率高
- 模板相比于函数重载, 更容易维护.
类模板:
像vector, list等等这些容器的底层实现都是通过类模板来实现的.
#include <iostream>
#include <typeinfo>
using namespace std;
template<class T>
class Tmp {
public:
void show() {
cout << typeid(T).name() << endl;
}
};
int main() {
Tmp<char>().show();
Tmp<short>().show();
Tmp<int>().show();
Tmp<float>().show();
Tmp<double>().show();
Tmp<long long>().show();
return 0;
}
模板实例化和匹配原则
实例化:
函数模板实例化: 根据传入参数类型实例化(惰性实例化)
1.隐式实例化: 能根据参数类型直接推演
Swap(1, 2) Swap(1.0, 2.0) ...
2.显示实例化: 编译器不能根据参数类型直接推演, 需显示指定
Swap(1, (int)2.0)
Swap<int>(1, 2.0)
类模板实例化: 需指定类型
vector<int> iv;
vector<double> dv;
匹配原则:
非模板函数是可以和模板函数同名的.
1.默认先与非模板函数进行匹配, 如果没有合适的再与模板匹配实例化.
Swap(1, 2);
2.可以通过特化模板来显示与模板匹配实例化:
Swap<int>(1, 2);
非类型模板参数
模板参数中可以有非类型的参数(常量), 这个常量必须是整型家族.
非模板参数在编译阶段就能确定
// 定义一个模板类型的静态数组
#include <iostream>
#include <assert.h>
using namespace std;
template<class T, size_t N = 10>
class Array {
public:
Array() : _size(N) {}
T& operator[](size_t index) {
assert(index < _size);
return _array[index];
}
const T& operator[](size_t index) const {
assert(index < _size);
return _array[index];
}
size_t Size() const {
return _size;
}
bool Empty() const {
return _size == 0;
}
private:
T _array[N];
size_t _size;
};
模板的特化
无论是函数模板的特化还是类模板的特化, 其实就是对特殊类型参数进行特殊处理.
全特化:
#include <iostream>
#include <string.h>
using namespace std;
template<class T>
bool equal(T left, T right) {
return left == right;
}
template<> //针对字符数组而言, 进行特殊化处理.
bool equal<char*>(char* left, char* right) {
return strcmp(left, right);
}
int main() {
//const char* s1 = "sock";
//const char* s2 = "sock";
char s1[] = "sock";
char s2[] = "sock";
bool judge = equal(s1, s2);
cout << judge << endl;
return 0;
}
模板类的全特化类似, 就是对模板参数全部特殊化的结果.
偏特化:
模板函数不支持偏特化.
#include <iostream>
using namespace std;
template<class T1, class T2>
class Add {
public:
T1 sum(const T1& a, const T2& b) {
return a + b;
}
};
// 对模板参数进行进一步特化
template<class T1, class T2>
class Add<T1*, T2*> {
public:
T1 sum(const T1* a, const T2* b) {
return *a + *b;
}
};
// 对部分模板参数进行特化
template<class T1>
class Add<T1, char*> {
public:
T1 sum(const T1& a, const char* b) {
return a + *b;
}
};
类型萃取:
类型萃取就是通过模板的特化来确定处理的对象是内置类型还是自定义类型, 进而根据不同类型来通过不同方法进行处理来提高程序的效率.
(POD类型: 通过二进制拷贝还能保证其数据不变的类或者结构体)
比如说深浅拷贝问题, 对于POD类型可用memcpy,memset来实现赋值要比调用赋值构造效率更高. 还比如动态数组或顺序表的扩容问题, 对于内置类型的扩容用 malloc, realloc 要比 new 效率更高.
#include <iostream>
using namespace std;
template<class T>
struct TypeTraits {
static bool IsPODType() {// 默认为非POD类型
return false;
}
};
// 对POD类型进行特化
template<>
struct TypeTraits<char> {
static bool IsPODType() {
return true;
}
};
template<>
struct TypeTraits<unsigned char> {
static bool IsPODType() {
return true;
}
};
template<>
struct TypeTraits<int> {
static bool IsPODType() {
return true;
}
};
template<>
struct TypeTraits<unsigned int> {
static bool IsPODType() {
return true;
}
};
//...
//所有的内置类型都特化一下
template<class T>
void Copy(T* dest, const T* src, size_t size) {
if (TypeTraits<T>::IsPODType()) {
memcpy(dest, src, size);
} else {
size_t i;
for (i = 0; i < size; ++i) {
dest[i] = src[i];
}
}
}
// 对于是否为POD类型进行不同的赋值
模板分离编译
一般来讲, 模板是不支持分离编译的, 这与模板的实现原理相关, 模板在传入参数之前是未实例化的, 并且每个源程序在编译阶段是相互独立的. 如果分离编译, 在链接阶段会产生链接错误, 因为编译器不能通过函数地址找到实例函数(因为根本没有实例化). 可以通过显示实例化来解决问题, 不过显示实例化要实例化所有需要类型的函数特别鸡肋, 最好是避免分离编译, 使得模板的定义和实现都在头文件中.