读书笔记--C++ Template(The complete guide)--Chapter3--类模板
上一篇讲到函数模板,自然需要接下来讲讲类模板,通俗点说,类模板就是带类型参数的类,它表示一族类,这些类的实现逻辑是一致的,STL中的容器类就是这一思想的典型应用,在这一章里,我们将用类模板来实现一个Stack的模板类(类模板?好像差不多)。
1.一个使用类模板的例子—Stack类
//bascis/stack1.hpp
#include <vector>#include <stdexcept>template<typename T>class stack{
private:
std::vector<T> elems;public:
void push(T const &);void pop();
T top()const;
bool empty()const{return elems.empty();
}};template<typename T>void Stack<T>::push(T const& elem){elems.push_back(elem);}template<typename T>void Stack<T>::pop()
{if(elems.empty())
{throw std::out_of_range("Stack<>::pop():empty stack");}elems.pop();}template <typename T>T Stack<T>::top()const
{if(elems.empty())
{throw std::out_of_range("Stack<>::top():empty stack");}return elem.back();
}
模板类中的数据用vector来储存,目的是为了排除冗杂反复的细枝末节,从而是我们能关注模板类的模板特性。
2.模板类的声明
声明模板类与声明模板函数差不多,也是需要有一行声明
template<typename T>
声明的格式同样是template<comma-sparated-typename-list>
在之前给出的代码中,Stack是类的名称,Stack<T>是类的类型,这是需要注意的,在需要类型的地方,请用Stack<T>,反之,用Stack,如:
template<typename T>class Stack
{...Stack(Stack<T>const&);
Stack<T>&&operator=(Stack<T>const&);...}
3.模板类的成员函数
模板类的成员函数与普通的模板函数用法几乎一致,只是加上了属于某个类的束缚,不详述了。
4.怎么使用模板类
以刚才的Stack为例:
//basics/stack1test.cpp
#include <iostream>#include <string>
#include <cstdlib>#include "stack1.hpp"
int main()
{try{
Stack<int> intStack;
Stack<std::string>stringStack;
intStack.push(7);std::cout<<intStack.top()<<std::endl;stringStack.push("hello");
std::cout<<stringStack.top()<<std::endl;stringStack.pop();stringStack.pop();}catch
{std::err<<"ExceptionL: "<<ex.what()<<std::endl;
return EXIT_FAILURE;
}}
可以看到,其实和普通的类用起来没多大区别,就是声明的时候带个参数。
模板类的使用时可以嵌套的,如下:
Stack<Stack<int> >intStackStack;
需要注意的是> >不能连着,否则会被识别成>>,导致错误(一直不理解编译器为什么连这种都识别不出)。
5.类模板的特化
具体来讲,类模板的特化允许对某些特定的类型采取更加有效的实现手段,或者是解决某些类型在用同样一套模板时会出现的行为异常(比如指针作为类模板的参数的话经常可能出现问题)。当然,一旦特化了某个类型参数,对应的成员函数全部需要特。第3小节说过,模板类的成员函数是跟普通的模板类差不多的,因此,它们当然也可以单独特化,但是这样的话,我们就不能特化整个类了。
要特化某个类模板,语法规则是前置一条生命:template<>,如:
tempalte<>class Stack<std::string>{...}
一旦特化之后,所有的成员函数都必须重新定义成普通成员函数,如:
//bascis/stack2.hpp
#include <vector>#include <stdexcept>template<> //特化标志class Stack<std::string>{//原来就是class Stackprivate:
std::vector<std::string> elems;
public:
void push(std::string const &);void pop();
std::string top()const;bool empty()const{return elems.empty();
}};//template<typename T> 不需要了
void Stack<std::string>::push(std::string const& elem){elems.push_back(elem);}void Stack<std::string>::pop(){if(elems.empty())
{throw std::out_of_range("Stack<>::pop():empty stack");}elems.pop();}std::string Stack<std::string>::top()const{if(elems.empty())
{throw std::out_of_range("Stack<>::top():empty stack");}return elem.back();
}
6.部分特化
侯捷先生称之为偏特化,可能是是他们的翻译习惯吧,记得偏最小二乘回归的英文就是PLSR,第一个单词就是partial,部分特化英文为partial specialization,这是一个非常值得讲一讲的问题,因为这是STL中trait编程技法的一个重要理论基础(有空的话会讲讲这方面的内容)。
那么什么是部分特化呢?C++ template给出的说法是这样的:
You can specify special implementations for particular circumstances,but some template parameters must still be defined by the users.
也就是说,对于某些特定的使用环境,你可以为模板类定义特殊的实现,对于部分特化而言,还是需要用户指定一些参数,假设原来有个模板类如下:
部分特化有以下几种:template<typename T1,typename T2>class MyClass
{...};
- 当两个模板参数的类型一致时:
template<typename T>class MyClass<T,T>
{...};- 某个模板参数需要固定
template<typename T>class MyClass<T,int>{...};- 模板参数是指针
template<typename T1,typename T2>class MyClass<T1*,T2*>
{...};
具体使用时,选用哪个是有规则的,如果只有一个严格匹配的,自然是选严格匹配的,如:MyClass<int,float>mif; //uses MyClass<T1,T2>MyClass<float,float>mff; //uses MyClass<T,T>MyClass<float,int>mfi;//use MyClass<T,int>MyClass<int * float*>mp;//use MyClass<T1*,T2*>
但是,有时候不只一个匹配的,而且匹配程度一样,就会发生编译错误:
MyClass<int,int>m; //Error: matches MyClass<T,T> and MyClass<T,int>MyClass<int *,int *>m;//Error: matches MyClass<T,T> and MyClass<T1* ,T2*>匹配实际上是有强有弱的,如果编译器能够识别出匹配的强弱,选强的那个,对于模板匹配,越特化的那个越强,比如说,对于上面例子的第二个情况,我们可以再特化一个模板类:
template<typename T>class MyClass<T* ,T*>
{...};
这个模板类非常强化,以至于编译器会直接选用这个,关于强弱在12章里面会有详细解释,大概意思就是如果A能表示B,B不能表示A,那么说明B的特化更强。
7.类模板的默认参数
与函数模板不一样的是,类模板是可以拥有默认参数的,甚至默认参数可以用到模板标识前面的类型参数,请看这个例子:
//bascis/stack3.hpp
#include <vector>#include <stdexcept>template<typename T,typename CONT =std::vector<T> >class stack{
private:
CONT elems;public:
void push(T const &);void pop();
T top()const;
bool empty()const{return elems.empty();
}};template<typename T,typename CONT =std::vector<T> >void StacK<T,CONT>::push(T const& elem){elems.push_back(elem);}template<typename T,typename CONT =std::vector<T> >void StacK<T,CONT>::pop()
{if(elems.empty())
{throw std::out_of_range("Stack<>::pop():empty stack");}elems.pop();}template <typename T>T StacK<T,CONT>::top()const
{if(elems.empty())
{throw std::out_of_range("Stack<>::top():empty stack");}return elem.back();
}
当然,你可以定义一个这样的具现化实例:
Stack<double,std::deque<double> >
8.总结
- 模板类是一族类的表示,有一个或多个用户指定类型作为参数
- 要使用模板类,传给它类型参数,模板据此生成特定的代码
- 模板类中的方法只有在使用到的时候才会具现化.也就是说即便某个类型不适合具现化,不能实现某些操作,只要这些操作没在已经用到的函数中,就不会发生问题.
- 模板类能够特化和部分特化.
- 模板类能定义默认的模板类型参数.