记录一下学习C++模板时的知识点
两阶段模板编译检查
模板的检查分成两阶段,在定义时和在实例化时进行检查。在模板定义时检查:
- 语法检查,比如分号。
- 使用了未知的类型或函数,且与模板参数无关。
在模板实例化阶段,会将类型带入模板再次进行检查,比如以下代码:
1 2 3 4 5 6 7 8
| template<typename T> void foo(T t) { undeclared(); undeclared(t); static_assert(sizeof(int) > 10,"int too small"); static_assert(sizeof(T) > 10, "T too small"); }
|
有些错误只会在模板实例化时出现,因此如果模板只是定义了,但是没有实例化过,这个错误可能一直存在但是不会被发现。
模板的定义
模板通常在声明时定义,但也支持声明与定义分离,例如在同一个头文件中
1 2 3 4 5 6 7 8 9 10
| template <typename T> class MyClass { public: void memberFunc(); };
template <typename T> void MyClass<T>::memberFunc() { }
|
处于编译效率考虑,也可以将定义放在其他头文件中,有些项目将包含有模板定义的文件添加在_impl.h后缀,供需要实例化的编译单元包含。
1 2 3 4 5 6 7
| template <typename T> void MyClass<T>::memberFunc() { }
template class MyClass<int>; template class MyClass<double>;
|
将模板定义移到单独头文件的核心价值是 通过显式实例化控制代码生成,从而优化编译速度和封装性,但会牺牲模板的灵活性(用户无法随意指定新类型)。
模板的默认参数
一个有意思的地方,即使后面的模板参数没有指定默认指,依然可以让前面的模板参数有默认值
1 2 3 4 5
| template<typename RT = long, typename T1, typename T2> RT max (T1 a, T2 b) { return b < a ? a : b; }
|
模板特化
什么是模板特化
为特定类型的模板提供一个“特殊”版本,作为模板定义的一个补充。模板的特化可以分成全特化和偏特化,全特化即完全指定所有模板参数,偏特化则为指定部分模板参数。
1 2 3 4 5 6 7 8 9 10 11
| template<typename T> bool equal(T a, T b) { return a == b; }
template <> bool equal(double a, double b) { return abs(a-b) < 1e-5; }
|
模板函数
实际上模板函数不存在模板偏特化,类似模板偏特化的功能是通过函数重载实现的。原因是函数重载可以实现模板特化的功能,就不再需要引入更加复杂的模板偏特化功能了。以下是利用函数重载实现模板偏特化的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| template <typename T, typename U> void foo(T a, U b) { std::cout << "Generic: T=" << typeid(T).name() << ", U=" << typeid(U).name() << std::endl; }
template <typename U> void foo(int a, U b) { std::cout << "Overload (T=int): U=" << typeid(U).name() << std::endl; }
int main() { foo(1, 2); foo(1, "hello"); foo(3.14, 'a'); return 0; }
|
模板类
模板的成员函数特化有多种方式。可以对整个模板类进行特化,特化时需要特化所有的成员函数,如果在特化时没有定义该成员函数,那么在后续使用时也不能使用该成员函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include "bits/stdc++.h"
using namespace std;
template <typename T> class MyClass { public: void func1() { cout << "Generic func1\n"; } void func2() { cout << "Generic func2\n"; } };
template <> class MyClass<int> { public: void func1() { cout << "Specialized func1 for int\n"; } };
int main() { MyClass<int> a; a.func1(); a.func2(); return 0; }
|
上面这个例子特化了一个int类型版本的MyClass,在特化时没有特化func2函数,因此在a调用func2时会编译错误。特化后的模板类不会继承通用实现版本的成员函数。
除了对整个模板类进行特化,还允许对模板类的单个成员函数进行特化,这样就能保持特化时继承通用版本的成员函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <iostream> #include <typeinfo>
template <typename T> class Printer { public: void printType() { std::cout << "Generic type: " << typeid(T).name() << "\n"; }
void printSpecial(); };
template <> void Printer<int>::printSpecial() { std::cout << "Specialized for int!\n"; }
int main() { Printer<double> p1; p1.printType();
Printer<int> p2; p2.printType(); p2.printSpecial();
return 0; }
|
此时成员函数就与普通函数一致,遵循通用的规则,例如只能全特化而不支持偏特化。
模板别名
模板别名可以便于我们使用,有这么几种常用用法
- typedef 关键字定义:
typedef Stack<int> IntStack
- using 关键字定义(C++11):
using IntStack = Stack<int>
也可以alias封装出一个新模板,例如:
1 2 3 4 5 6 7 8
| template<typename T> using DequeStack = Stack<T, std::deque<T>>;
template<typename T> using EntityWithLogAndTimer = Logger<Timer<T>>;
using MyBetterEntity = EntityWithLogAndTimer<BaseEntity>;
|