Template
成员模板( Member Template )
C++ 模板, 除开较为常见的 class template
和 function template
, 还有一种 member template
. 它被大量运用在继承关系的构造函数中, 以实现通过派生类的拷贝构造基类函数.
1
2
3
4
5
6
7
8
9
10
11
12
// class template
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
// function & member template: 特指构造函数的形参类型与类的类型不一样
template <class U1, class U2>
pair(const pair<U1, U2> &p) : first(p.first), second(p.second) {}
};
可以通过以下代码来理解, 什么是”通过派生类的拷贝构造基类函数”
1
2
3
4
5
6
7
8
9
10
11
// declaration
class base1 {...};
class derive1 : public base1 {...};
class base2 {...};
class derive2 : public base2 {...};
// correct: up-cast
pair<base1, base2> p(pair<derive1, derive2>());
// error
pair<derive1, derive2> p(pair<base1, base2>());
up-cast 向上映射, 指的是派生类指针向上( 继承关系箭头 )映射为基类. 如
base1 *p = new derive1();
. 因此就有了模板成员的诞生, 模板成员的这种做法能够实现向上映射, 因此这也是 STL 中对容器( Container )的设计.
(全)特化( Specialization )
特化与泛化相对, 指的就是指定一个特定的类型, 用 STL 中的 hash
举例. 当同时存在泛化与特化时, 优先使用特化代码.
1
2
3
4
5
6
7
8
9
10
11
12
13
// generalization
template <class Key>
struct hash { };
// specialization
template <>
struct hash <char>
{ ... };
template <>
struct hash <int>
{ ... };
template <>
struct hash <long>
{ ... };
偏特化( Partial Specialization )
- 参数个数上的偏特化: 多个模板参数绑定其中部分的情况称为偏特化( 部分特化 )
1
2
3
4
5
6
7
// generalization
template <typename T, typename Alloc=...>
class vector { ... };
// partial specialization: bool 被绑定
template <typename Alloc=...>
class vector<bool, Alloc>
{ ... };
写 python 的时候有遇到过偏特化的报错, 如
train(dir='./', model, saveDir='')
是错误的. 偏特化的参数必须连续, 不能中间间隔中断. C++也一样.
- 参数范围上的偏特化: 任意类型偏特化为任意类型的指针
1
2
3
4
5
6
// generalization
template <typename T>
class myclass { ... };
// partial specialization
template <typename U>
class myclass<U*> { ... };
这里偏特化模板参数之所以用
U
, 只是为了说明与泛化类型毫无关系
嵌套模板参数( 模板模板参数 Template Template Parameter )
代码表述更清晰一点, 直接上代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// declare
template <typename T, template <typename T> class Container>
class XCLs
{
private:
Container<T> c;
...
};
// use
template <typename T>
// wrong
XCLs<string, list> wrongList;
// correct
XCLs<string, list<T, allocator<T>>> mylist;
只有在
template
的参数中,typename
与class
作用相同
这里需要注意的是嵌套模板参数的使用语法, 直接通过 list
给容器赋值, 试图使用 list
的默认模板参数( 比如 allocator
)赋值, 然而编译器不通过会报错, 必须指明第二个模板参数. 针对需要接受多个模板参数的类时, 调用这种类似嵌套模板的类时, 需要指明所有模板参数.
因此, 若一个类只接受一个模板参数时, 那么就可以编译通过, 如下:
1
2
3
4
5
6
7
8
9
10
template <typename T, template <typename T> class SmartPtr>
class XCLs
{
private:
SmartPtr<T> ptr;
...
};
// correct: 以下 shared_ptr 与 auto_ptr 模板只接受一个参数
XCLs<string, shared_ptr> p1;
XCLs<string, auto_ptr> p1;
与绑定类型的模板参数的区别
先看代码, 判断以下代码是否是嵌套模板参数
1
2
3
4
5
template <class T, class Sequence = deque<T>>
class stack { ... };
// use
stack<int> stk;
stack<int, list<int>> listStk;
实际上, 上述代码不是嵌套模板参数. 原因可以参见使用的代码, 在使用时已经确定了 stk
& listStk
绑定的参数. 与前文依旧未绑定参数不同.
C++11: 变参模板( Variadic Template )
C++11引入的新特性, 使得模板参数的个数可变, 不再是确定个数, 以递归打印为例, 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
// 省略 << 重载
ostream& operator<< (ostream& os, myclass &m) {};
void print() {}
template <typename T, typename... Types>
void print(const T &firstArg, const Types &...args)
{
cout << firstArg << endl;
print(args...);
}
// use
print(1, 0.5, "hello world", myclass(5,5,5));
print
每次将诸多参数拆分为 1 + pack, 每次输出拆出来的那一个参数, 当 pack 内无参数时, 调用最上面重载的无参数 print
. 当然这里只是针对递归输出举例, 可以对拆出来的一个参数与剩下的包进行你需要的任何操作与实现.
此处代码中的
...
不再是其余篇章中为了省略实现的省略号, 而是非常重要的语法
可以通过
sizeof...(args)
来得知包中的参数个数