模板使用技巧基础
这篇文章主要记下实际工程中使用模板的一些基本技巧,其中主要包括typename关键字的使用,定义模板成员函数,定义嵌套类模板,双重模板类型参数(template template parameter),以及零值初始化的技巧。
一、关键字typename
关键字typename是C++标准化过程中被引入的,目的是告诉编译器模板类型参数或者其内的某个标识符是个类型。
template <typename T>
class MyClass{
public:
typename T::subtype *ptr;
... ...
};
第二个typename关键字说明subtype是T内部定义的一个类型,所以ptr是一个指向subtype的指针。如果移除掉typename关键字修饰,那么subtype会被编译器误认为是T内部的一个静态成员,所以 T::subtype * ptr 就变成了一个表达式。
通常如果某个与模板参数相关的名称是个类型时,前面就需要添加关键字typename.
二、.template构件
在引入typename之后,又出现了一个类似问题。如下面的代码所示
template<int N>
void printBitset (std::bitset<N> const& bs)
{
std::cout << bs.template to_string<char,char_traits<char>,
allocator<char> >();
}
.template构件显得比较古怪,如果没有它,编译器不会认为紧跟其后的‘<’ 是模板参数列表的开始,而非一个小于号。
小结:.template构件只有在其前的构件(本例是bs)依赖于模板类型参数时,才会被使用。
三、使用this->
template <typename T>
class Base {
public:
void exit();
};
template <typename T>
class Derived : Base<T> {
public:
void foo() {
exit(); // calls external exit() or error
}
};
如果class templates拥有base classes,那么其出现的成员名称'exit()'并非总是等价于‘this->exit()',即base<T>::exit()。应在调用前,显式添加’this->' 或‘base<T>::'才可通过编译。
使用建议准则:任何使用基类继承而来的成员或成员函数,如果基类与模板类型参数相关,那么最好在使用前加上'this->'或者’base<T>::'的修饰。
四、成员模板
成员模板可以是嵌套模板类也可以是成员函数模板。
// basics/stack6decl.hpp
template <typename T, typename CONT = std::deque<T> >
class Stack {
private:
CONT elems; // elements
public:
void push(T const&); // push element
void pop(); // pop element
T top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
// assign stack of elements of type T2
template <typename T, typename CONT2>
Stack<T,CONT>& operator= (Stack<T,CONT2> const&);
};
// basics/stack6assign.hpp
template <typename T, typename CONT>
template <typename T, typename CONT2>
Stack<T,CONT>&
Stack<T,CONT>::operator= (Stack<T,CONT2> const& op2)
{
if ((void*)this == (void*)&op2) { // assignment to itself?
return *this;
}
Stack<T> tmp(op2); // create a copy of the assigned stack
elems.clear(); // remove existing elements
while (!tmp.empty()) { // copy all elements
elems.push_front(tmp.top());
tmp.pop();
}
return *this;
}
通过上述成员赋值模板函数,就可完成以deque实现的stack赋值到以vector实现的stack,不过stack中保存的元素类型仍然相同。
五、双重模板类型参数(Template Template Parameter)
template <typename T, typename CONT = std::deque<T> >
class Stack {
...
};
如此声明,在使用不同container作为stack的实现时,需要再次重复指定元素类型。如果使用ttp的话,那么就可以不需要重复指定元素类型了。那么ttp到底是个什么东东呢?实际上ttp就是在声明时告诉编译器模板类型参数本身也是一个模板类。按照ttp原则,这个例子中,stack的container模板类型参数本身也是一个类模板。所以修改如下
template<
typename T,
template<typename ELEM>class CONT=std::deque
>
class Stack {
... ...
};
如果按照ttp声明stack模板类时,那么在使用不同container作为stack的实现时,就不再需要重复指定元素类型了。
技巧1:因为TTP中的模板类型参数(ELEM)并没有被使用,所以形式上可以忽略。所以下面的这个声明也可以
template<
typename T,
template<typename>class CONT=std::deque
>
class Stack {
... ...
};
注意事项:
成员函数模板的声明及实现中的模板类型参数也需要按照TTP原则做出相同的改动。
模板模板参数的匹配(Template Template Argument Matching)
如果试图使用按照TTP原则修改后的class templates stack,编译器会报错说std::deque不符合template template parameter CONT的要求,原因是TTP原则不仅要求template parameter本身是个class template,而且这个class template的参数必须严格匹配它所替换的TTP的参数。
再次考虑本例,原本代表container的那个template parameter是 std::deque<T, alloc<T> >, 很显然这个container是需要两个template parameter的,但是当我们之前用以替换的class template却只声明了一个template parameter,这就导致了参数匹配不成功。所以需要进一步修改一下class template Stack的声明如下
template <typename T,
template <typename ELEM, typename ALLOC = std::allocator<ELEM> >
class CONT = std::deque>
class Stack {
private:
CONT<T> elems; // elements
…
};
PS: 似乎为了少写一个T, 却使class template声明变得很复杂。
六、零值初始化
template <typename T>
void foo()
{
T x = T(); // x has undefined value if T is built-in type
}
假定T并无默认构造函数时,x的初值就无法被正确初始化,进而引发未定义的行为。因此在定义class template时,一定要定义默认构造函数,初始化其内部成员。当然,如果T本身是内建类型时,如int, bool等, 当默认构造函数被调用后,其值就会被初始化为0。
小结:
1. 当要操作一个取决于template parameter的类型名称时,应该在前面加typename关键字修饰。
2. 嵌套类和成员函数也可以是模板。
3. 赋值运算符的template版本并不取代默认赋值运算符。
4. 把class template作为template parameter使用的技巧成为Template Template Parameter。
5. Template Template Arguments必须完全匹配其对应参数。
6. 当具现化一个内建类型的变量时,如果需要设定初值,必须明确调用其默认构造函数。