技巧性基础知识
这章中主要讲述了一些与模板实际应用密切相关的一些更加深入的基础知识。主要包括:
1. 关键字 typename
2.使用this->
3.成员模板
4.模板的模板参数
5.零初始化
6.字符串作为函数模板的实参
1. 关键字typename
在C++中,引入typename是为了说明:模板内部的标识符可以是一个类型。譬如下面:
1 template <typename T> 2 class MyClass { 3 typename T::SubType * ptr; 4 .... 5 };
上面第三行的typename就是用来说明SubType是个类型,ptr是个指向SubType类型的指针,而不是表示SubType与ptr的乘积。
对于如下示例:
1 template <int N> 2 void printBitset (std::bitset<N> const& bs) { 3 std::cout << bs.template to_string<char, char_traits<char>, allocator<char> >(); 4 }
在此处,如果不使用.template这个构造,编译器将不知道bs.template后面的小于号是模板参数列表的起始符号,引起这个的主要原因是传入参数bs本身依
赖于参数N的构造。
因此,只有当该前面存在依赖于模板参数的对象时,我们需要在模板内部使用.template标记,而此标记也只能在模板内部使用。
2. this->的使用
对于如下示例:
1 #include <iostream> 2 3 template <typename T> 4 class Base { 5 public: 6 void exit () { 7 std::cout << "Base::exit() !" << std::endl; 8 } 9 }; 10 11 template <typename T> 12 class Derived : Base<T> { 13 public: 14 void foo() { 15 std::cout << "Derived::foo()!" << std::endl; 16 this->exit (); //注意此处 17 } 18 }; 19 20 21 int main (){ 22 Derived<int> der; 23 der.foo(); 24 return 0; 25 }
很明显,会输出:
Derived::foo()!
Base::exit() !
如果将16行的this->去掉则会输出:
Derived::foo()!
在这个例子中,foo()内部决定调用那一个exit()时,并不会考虑基类Base中定义的exit()。因此,如果不加上this->,不是调用了一个错误的exit()函数,
就是获得了一个编译错误。
因此,对于那些在基类中声明,并且依赖于模板参数的符号(函数或者变量等),应该在它们前面使用this->或者Base<T>::。
3. 成员模板
类的成员也可以是模板,嵌套类和成员函数都可以作为模板。如下示例:
1 template <typename T> 2 class Stack { 3 private: 4 std::deque<T> elems; 5 6 public: 7 void push (T const&); 8 void pop(); 9 T top () const; 10 bool empty () const { 11 return elems.empty (); 12 } 13 14 //使用元素类型为T2的栈进行赋值 15 template <typename T2> //成员模板 16 Stack<T>& operator= (Stack<T2> const&); 17 }; 18 19 template <typename T> 20 template <typename T2> 21 Stack<T>& Stack<T>::operator= (Stack<T2> const& op2){ 22 if ((void*)this == (void*)&op2) { 23 return *this; 24 } 25 26 Stack<T2> tmp(op2); 27 28 elems.clear(); 29 while (!tmp.empty()) { 30 elems.push_front(tmp.top()); 31 tmp.pop(); 32 } 33 return *this; 34 };
在上段示例中第15 16行在定义有模板参数T的模板内部,定义了一个含有模板参数T2的内部模板:
template <typename T>
template <typename T2>
...
4. 模板的模板参数
根据名字,我们很容易想到,此处就是说,将一个模板作为一个模板参数来使用。示例如下:
template <typename T, template <typename ELEM> class CONT = std::deque > class Stack { private: CONT<T> elems; public: ... };
在这个例子中,我们使用了第一个模板参数作为第二个模板参数的实例化类型。一般来说,我们可以使用类型模板内部的任何类型来实例化模板的模板参数。
当第二个模板参数,即模板的模板参数有缺省值的时候,会有可能,因为在声明的时候未指定这个缺省值而产生 缺省值与模板的模板参数不匹配的错误信息。 因此对于将具有缺省值的模板作为模板参数时,可以在使用时指定缺省值。
5. 零初始化
在编写模板时,如果希望模板类型的变量使用缺省值初始化,我们需要显示的调用内建类型的缺省构造函数,如int()可以获得缺省值 0 。代码如下:
1 template <typename T> 2 void foo () { 3 T x = T (); //如果T是内建类型,x可以初始化为0或者false 4 }
6. 使用字符串作为函数模板的实参
如下代码:
1 template <typename T> 2 inline T const& max (T const& a, T const& b){ 3 return a < b ? b : a; 4 } 5 6 int main (){ 7 std::string s; 8 9 ::max ("apple", "peach"); //OK: 相同类型的实参 10 ::max ("apple", "tomato"); //ERROR:不同类型的实参 11 ::max ("apple", s); //ERROR:不同类型的实参 12 }
上述的问题在于这些字符串属于不同类型的数组类型。他们的类型分别是:
"apple" char const[6]
"peach" char const[6]
"tomato" char const[7]
s std::string
因此上述调用时会出现不同的类型实参从而产生错误。
解决的方法是:
1). 对于字符串的调用,使用非引用参数
2). 进行重载,编写接受引用参数和非引用参数两个重载函数 (注意二义性)
3). 对具体类型进行重载 (比如对 std::string 进行重载)。
4). 重载数组类型,比如:
1 template <typename T, int N, int M> 2 T const * max (T const (&a)[N], T const (&b)[M]) { 3 return a < b ? b : a; 4 }
5). 强制要求应用程序员使用显示类型转换