C++ Templates:技巧性基础知识(来自《C++ template》)
关键字typename:
引入关键字typename是为了说明:模板内部的标识符可以是一个类型:
1: template <typename T>
2: class Myclass {
3: typename T::SubType *ptr;
4: }
.template构造:
1: void printBitset (std::bitset<N> const& bs) {
2: std::cout<< bs.template to_string< char, char_traits<char>, allocator<char> >();
3: }
.template告诉编译器.template后面的小于号是模板实参列表的起始符号。
使用this->:
对于那些在基类中声明,并且依赖于模板参数的符号(函数或变量等),应该在它们前面使用this->或者Base<T>::。
如果希望完全避免不确定性,可以限定模板中所有的成员访问。
成员模板和模板的模板参数:
通常,栈之间只有在类型完全相同时才能相互赋值,其中类型指的是元素的类型,即使这两种元素的类型之间存在隐式类型转换。‘
函数模板不支持模板的模板参数。
不同类型间赋值的解决办法(并加入模板的模板参数的完整版):
1: #include <deque>
2: #include <stdexcept>
3: #include <memory>
4: template <typename T,
5: template <typename ELEM, typename = std::allocator<ELEM> > class CONT = std::deque> //模板的模板参数,其中类的两个模板参数是为了匹配std::deque的两个模板实参缺省模板参数(元素类型和内存分配器allocator)
6: class Stack {
7: private:
8: CONT<T> elems; // 由T来决定类模板中的元素的类型,实例化时可直接写为:Stack<int, std::vector> vStack
9: public:
10: void push(T const&); // push element
11: void pop(); // pop element
12: T top() const; // return top element
13: bool empty() const { // return whether the stack is empty
14: return elems.empty();
15: }
16: //在元素不同的栈之间相互赋值的重载operator=
17: template<typename T2,
18: template<typename ELEM2, typename = std::allocator<ELEM2> >class CONT2>
19: Stack<T,CONT>& operator= (Stack<T2,CONT2> const&);
20: };
21: template <typename T, template <typename,typename> class CONT>
22: void Stack<T,CONT>::push (T const& elem)
23: {
24: elems.push_back(elem); // append copy of passed elem
25: }
26: template<typename T, template <typename,typename> class CONT>
27: void Stack<T,CONT>::pop ()
28: {
29: if (elems.empty()) {
30: throw std::out_of_range("Stack<>::pop(): empty stack");
31: }
32: elems.pop_back(); // remove last element
33: }
34: template <typename T, template <typename,typename> class CONT>
35: T Stack<T,CONT>::top () const
36: {
37: if (elems.empty()) {
38: throw std::out_of_range("Stack<>::top(): empty stack");
39: }
40: return elems.back(); // return copy of last element
41: }
42: //在元素不同的栈之间相互赋值的重载operator=
43: template <typename T, template <typename,typename> class CONT>
44: template <typename T2, template <typename,typename> class CONT2>
45: Stack<T,CONT>& Stack<T,CONT>::operator= (Stack<T2,CONT2> const& op2)
46: {
47: if ((void*)this == (void*)&op2) { // 自身赋值检测
48: return *this;
49: }
50: Stack<T2,CONT2> tmp(op2); // 创建一份赋值栈的拷贝,以便使用top()和pop()从该拷贝中获取元素。
51: elems.clear(); // 移除现有元素
52: while (!tmp.empty()) { // 拷贝所有元素
53: elems.push_front(tmp.top());
54: tmp.pop();
55: }
56: return *this;
57: }
零初始化:
对于int、double或者指针等基本类型,应该显式地调用内建类型的缺省构造函数,并把缺省值设为0(或者false,对于bool类型而言)。比如调用int()我们就将获得缺省值0。
1: template <typename T>
2: void foo() {
3: T x = T() ;
4: }
对于类模板,需要定义一个缺省构造函数,通过一个初始化列表来初始化类模板的成员。
1: template <typename T>
2: class Myclass {
3: private:
4: T x;
5: public:
6: Myclass() : x()
7: ...
8: };
使用字符串作为函数模板的实参:
如果把字符串传递给函数模板的引用参数时,由于长度的区别,长度不同的字符串属于不同的数组类型,可以通过传递给非引用类型的参数来解决这个问题,但对于非引用类型的参数,在实参演绎的过程中,会出现数组到指针的类型转换。
根据不同的情况,对此类问题的解决方法:
使用非引用参数,取代引用参数(然而,可能会导致无用的拷贝)
进行重载,编写接收引用参数和非引用参数的两个重载函数(然而,可能会导致二义性)
对具体类型进行重载(比如对std::string进行重载)
重载数组类型,如:
template <typename T, int N, int M>
T const* max(T const (&a)[N], T const (&b)[M]) {
return a < b ? b : a;
}
强制要求应用程序程序员使用显式类型转换。
小结:
如果要访问依赖于模板参数的类型名称,应该在类型名称前添加关键字typename。
嵌套类和成员函数也可以是模板。
类模板也可以作为模板参数,称之为模板的模板参数。
模板的模板实参必须精确地匹配。
通过显式调用缺省构造函数,可以确保模板的变量和成员都已经用一个缺省值完成初始化,这种方法对内建类型的变量和成员也适用。
对于字符串,在实参演绎过程中,当且仅当参数不是引用时,才会出现数组到指针的类型转换