读书笔记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 Stack
private:
	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.总结

  • 模板类是一族类的表示,有一个或多个用户指定类型作为参数
  • 要使用模板类,传给它类型参数,模板据此生成特定的代码
  • 模板类中的方法只有在使用到的时候才会具现化.也就是说即便某个类型不适合具现化,不能实现某些操作,只要这些操作没在已经用到的函数中,就不会发生问题.
  • 模板类能够特化和部分特化.
  • 模板类能定义默认的模板类型参数.
原文地址:https://www.cnblogs.com/obama/p/3055229.html