这章的主要内容包括:
- 非类型的类模板参数。
- 非类型的函数模板参数。
- 非类型模板参数的限制。
一:非类型的类模板参数。
我认为类模板的非类型参数相当于一个全局常量的角色。书中举了下面的例子来说明非类型的类模板。这一章通过重新定义一个Stack模板,要求使用一个固定大小的数组作为元素的容器,并且数组的大小可以由模板的使用者自己定义。那么,对于模板的设计者,就应该提供一个接口使得使用者可以定义数组的大小。这就需要用到非类型的类模板参数。下面的代码能很好的解释这个问题:
1
#include
<
iostream
>
2 #include < string >
3 #include < cstdlib >
4 #include < stdexcept >
5
6 template < typename T, int MAXSIZE >
7 class Stack{
8 private :
9 T elems[MAXSIZE];
10 int numElems;
11
12 public :
13 Stack();
14 void push(T const & );
15 void pop();
16 T top() const ;
17 bool isEmpty() const {
18 return numElems == 0 ;
19 }
20
21 bool isFull() const {
22 return numElems == MAXSIZE;
23 }
24 };
25
26 template < typename T, int MAXSIZE >
27 Stack < T, MAXSIZE > ::Stack():numElems( 0 )
28 {
29 // 不作任何事,仅为了初始化numElems。
30 }
31
32 template < typename T, int MAXSIZE >
33 void Stack < T, MAXSIZE > ::push(T const & elem)
34 {
35 if (numElems == MAXSIZE)
36 {
37 throw std::out_of_range( " Stack<>::push()==>stack is full. " );
38 }
39
40 elems[numElems] = elem;
41 ++ numElems;
42 }
43
44 template < typename T, int MAXSIZE >
45 void Stack < T, MAXSIZE > ::pop()
46 {
47 if (numElems <= 0 )
48 {
49 throw std::out_of_range( " Stack<>::pop: empty stack " );
50 }
51
52 -- numElems;
53 }
54
55 template < typename T, int MAXSIZE >
56 T Stack < T, MAXSIZE > ::top() const
57 {
58 if (numElems)
59 {
60 throw std::out_of_range( " Stack<>::pop: empty stack " );
61 }
62
63 // 返回最后一个元素。
64 return elems[numElems - 1 ];
65 }
66
67 int main()
68 {
69 try
70 {
71 Stack < int , 20 > int20Stack;
72 Stack < int , 40 > int40Stack;
73 Stack < std:: string , 40 > stringStack;
74
75 int20Stack.push( 7 );
76 std::cout << " int20Stack.top() : " << int20Stack.top() << std::endl;
77 int20Stack.pop();
78
79 stringStack.push( " HelloWorld! " );
80 std::cout << " stringStack.top() : " << stringStack.top() << std::endl;
81 stringStack.pop();
82 stringStack.pop();
83 }
84 catch (std::exception const & ex)
85 {
86 std::cerr << " Exception: " << ex.what() << std::endl;
87
88 return EXIT_FAILURE;
89 }
90
91 return 0 ;
92 }
上面的代码揭示了非类型的类模板参数的定义和使用方法。需要注意的有:
2 #include < string >
3 #include < cstdlib >
4 #include < stdexcept >
5
6 template < typename T, int MAXSIZE >
7 class Stack{
8 private :
9 T elems[MAXSIZE];
10 int numElems;
11
12 public :
13 Stack();
14 void push(T const & );
15 void pop();
16 T top() const ;
17 bool isEmpty() const {
18 return numElems == 0 ;
19 }
20
21 bool isFull() const {
22 return numElems == MAXSIZE;
23 }
24 };
25
26 template < typename T, int MAXSIZE >
27 Stack < T, MAXSIZE > ::Stack():numElems( 0 )
28 {
29 // 不作任何事,仅为了初始化numElems。
30 }
31
32 template < typename T, int MAXSIZE >
33 void Stack < T, MAXSIZE > ::push(T const & elem)
34 {
35 if (numElems == MAXSIZE)
36 {
37 throw std::out_of_range( " Stack<>::push()==>stack is full. " );
38 }
39
40 elems[numElems] = elem;
41 ++ numElems;
42 }
43
44 template < typename T, int MAXSIZE >
45 void Stack < T, MAXSIZE > ::pop()
46 {
47 if (numElems <= 0 )
48 {
49 throw std::out_of_range( " Stack<>::pop: empty stack " );
50 }
51
52 -- numElems;
53 }
54
55 template < typename T, int MAXSIZE >
56 T Stack < T, MAXSIZE > ::top() const
57 {
58 if (numElems)
59 {
60 throw std::out_of_range( " Stack<>::pop: empty stack " );
61 }
62
63 // 返回最后一个元素。
64 return elems[numElems - 1 ];
65 }
66
67 int main()
68 {
69 try
70 {
71 Stack < int , 20 > int20Stack;
72 Stack < int , 40 > int40Stack;
73 Stack < std:: string , 40 > stringStack;
74
75 int20Stack.push( 7 );
76 std::cout << " int20Stack.top() : " << int20Stack.top() << std::endl;
77 int20Stack.pop();
78
79 stringStack.push( " HelloWorld! " );
80 std::cout << " stringStack.top() : " << stringStack.top() << std::endl;
81 stringStack.pop();
82 stringStack.pop();
83 }
84 catch (std::exception const & ex)
85 {
86 std::cerr << " Exception: " << ex.what() << std::endl;
87
88 return EXIT_FAILURE;
89 }
90
91 return 0 ;
92 }
- 非类型的类模板参数也是模板的参数之一。有某个非类型的模板参数的模板和没有那个非类型的模板参数的模板是两个不同的模板。
- 上面的int20Stack实例和int40Stack实例中,虽然实例化模板的时候都使用了int作为类型参数。但是他们仍然是两个不同的类的实例。他们之间没有任何的关系。不能相互进行类型转化,也不能赋值。这里需要注意,类模板的实例化在实质上是编译器将原来的模板拷贝一份,然后根据实例化参数替换原来的类型定义(也包括非类型的模板参数),从而成为一个新的类。也就是说,对于相同的实例化参数(包括非类型的模板参数)编译器都会实例化成为一个相同的类,只是我们不知道类名罢了。从这个角度上来说,上面的int20Stack和int40Stack是两个不同类的两个实例,他们之间没有任何关系,因为他们的模板实例化参数不一样。但是,如果定义为[Stack<int, 20> intStack1; Stack<int, 20> intStack2;]那么intStack1和intStack2可以相互赋值。因为他们对模板的实例化参数是一样的,从而都是同一类型的。这里有必要澄清一个事实,如果两个类有相同的成员函数和成员变量,他们生成的实例仍然是两个类型,与这里的情况完全不一样。
- 非类型的类模板参数也可以有缺省值。例如,template<typename T, int MAXSIZE = 100>...
二:非类型的函数模板参数。
函数模板的非类型参数主要用来为函数提供一个运算常量。关于非类型的函数模板参数,书中有下面的例子:
1
//
函数模板定义
2 template < typename T, int VAL >
3 T addValue(T const & x)
4 {
5 return x + VAL;
6 }
7
8 // 其他代码
9
10
11 // 函数模板的使用
12 std::transform(source.begin(), source.end(), dest.begin(),
13 ( int ( * ) ( int const & ))addValue < int , 5 > );
上面的代码中定义了一个函数模板,目的是对传入的参数加上一个指定的int型的5。这样的函数被普遍的使用在对一组数据进行同一处理的场合。例如,12行。这里需要注意的是:一
std::transform函数本身就是一个模板函数,它的最后一个参数可以传递一个函数指针。
因此,
(
int
(
*
) (
int
const
&
))addValue
<
int
,
5
>
其实是一个指向实例化后的
addValue
<T, int VAL>模板函数的指针。至于这个指针怎么读,还请高手指教。
另外需要注意的一点是,
std::transform
的最后一个参数不一定要是模板函数,任何函数都可以(关于std::transform的正确理解参考下面的评论)
。只是模板函数更合适处理多种类型的数据罢了。
2 template < typename T, int VAL >
3 T addValue(T const & x)
4 {
5 return x + VAL;
6 }
7
8 // 其他代码
9
10
11 // 函数模板的使用
12 std::transform(source.begin(), source.end(), dest.begin(),
13 ( int ( * ) ( int const & ))addValue < int , 5 > );
三:非类型模板参数的限制。
关于非类型模板参数的限制目前记住它可以是常整型(包括枚举类型)和指向外部连接对象的指针就可以可了。由于历史原因,浮点型不能作为非类型模板的参数;而指针和字符串作为非类型模板的参数是有条件的。我想这与变量的作用范围和生命周期有关吧。书中后面会有比较相信的介绍,就等到时候再细看了。