《C++Templates》笔记———2.类模板

1.类模板Stack的实现

类模板的声明和函数模板的声明相似,下面是一个类模板Stack的例子,这个类的类型是Stack,其中T是模板参数。然而,当时用类名而不是类的类型时,就应该只用Stack;比如,指定类的名称、类的构造函数、析构函数时。总结如下:

  • 和普通类的声明相似,先在类声明前加一行template ,然后将类声明中的数据类型换成虚拟类型参数T。
  • 类模板的成员函数在类模板外定义时必须指定成函数模板,且需要使用这个类模板的完整类型限定符Stack::。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#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<T>::pop(): empty stack");
}
elems.pop_back(); //删除最后一个元素
}

template <typename T>
T Stack<T>::top() const
{
if (elems.empty())
{
throw std::out_of_range("Stack<T>::pop(): empty stack");
}
return elems.back(); //返回最后一个元素的拷贝
}

2.类模板Stack的使用

类模板定义对象时形式为:类模板名<实际类型> 对象名或类模板名<实际类型> 对象名(实参表列)。例如,Stack intStack。

1
2
3
4
5
6
7
8
9
10
11
12
Stack<int>          intStack;      //元素类型为int的栈
Stack<std::string> stringStack; //元素类型为字符串的栈

//使用int栈
intStack.push(7);
intStack.push('a');
std::cout << intStack.top() << std::endl;

//使用string栈
stringStack.push("chdayj");
std::cout << stringStack.top() << std::endl;
stringStack.pop();

同时,模板实参可以为任何类型:

1
2
Stack<float*> floatStrStack;      //元素为浮点型指针的栈
Stack<Stack<int> > intStackStack; //元素为int栈的栈

注意,两个靠在一起的模板尖括号(即>) 之间要留一个空格;否则,编译器会误认为你在使用operator>>,从而导致语法错误。

3.类模板的特化

因为类模板中的定义不一定适用于所有数据类型,所以针对此类型应该重新定义,即所谓类模板的特化。如果要特化一个类模板,自然要特化此类模板所有的成员函数。

3.1.全特化

特化的类上面应该加着templlate<>,并且特化后的类的类型应该将T换成具体的数据类型,比如int,string等。同时,每个成员函数都必须重新定义为普通函数。下面是一个用std::string特化Stack<>的完整例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <deque>
#include <stdexcept>
#include <string>

template<>
class Stack<std::string>{
private:
std::deque<std::string> elems; //存储元素的容器
public:
void push(std::string const&); //压入元素
void pop(); //弹出元素
std::string top() const; //返回栈顶元素
bool empty() const { //返回栈是否为空
return elems.empty();
}
};

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<<std::string>>::pop(): empty stack");
}
elems.pop_back(); //删除最后一个元素
}

std::string Stack<std::string>::top() const
{
if (elems.empty())
{
throw std::out_of_range("Stack<<std::string>>::pop(): empty stack");
}
return elems.back(); //返回最后一个元素的拷贝
}

上面的例子中用deque而不是vector,说明:特化的实现可以和基本类模板的实现完全不同。

3.2.局部特化

可以在特定的情况下指定类模板的特定实现,并且部分模板参数依然必须有用户来定义。例如类模板:

1
2
3
4
template <typename T1, typename T2>
class MyClass{
...
};

可以有以下几种局部特化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//1.局部特化:两个模板参数具有相同的类型
template <typename T>
class MyClass<T, T>{
...
};

//2.局部特化:第2个模板参数类型为int
template <typename T>
class MyClass<T, int>{
...
};

//3.局部特化:两个模板参数都为指针类型
template <typename T1, typename T2>
class MyClass<T1*, T2*>{
...
};

下面的例子展示各种声明会使用哪个模板:

1
2
3
4
MyClass<int,float>   mif;     //使用MyClass<T1,T2>
MyClass<float,float> mFf; //使用MyClass<T,T>
MyClass<float,int> mfi; //使用MyClass<T,int>
MyClass<int*,float*> mp; //使用MyClass<T1*,T2*>

如果有多个局部特化同等程度地匹配某个声明,那么该声明就具有二义性:

1
2
MyClass<int,int>    m;     //ERROR:同时匹配MyClass<T,T>和MyClass<T,int>
MyClass<int*,int*> m; //ERROR:同时匹配MyClass<T,T>和MyClass<T1*,T2*>

为了解决例子2中二义性,你可以另外提供一个指向相同类型指针的特化:

1
2
3
4
template <typename T>
class MyClass<T*, T*>{
...
};

4.缺省模板实参

我们上面例子中的类模板Stack<>是通过C++标准库的vector<>来实现的;因此,我们不需要亲自实现内存管理、拷贝构造函数和赋值运算符。同时,类模板还可以为模板参数定义缺省值,这些值称为缺省模板实参。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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>
void Stack<T, CONT>::push(T const& elem)
{
elems.push_back(elem);
}

template <typename T, typename CONT>
void Stack<T, CONT>::pop()
{
if (elems.empty())
{
throw std::out_of_range("Stack<T, CONT>::pop(): empty stack");
}
elems.pop_back(); //删除最后一个元素
}

template <typename T, typename CONT>
T Stack<T, CONT>::top() const
{
if (elems.empty())
{
throw std::out_of_range("Stack<T, CONT>::pop(): empty stack");
}
return elems.back(); //返回最后一个元素的拷贝
}

可以看到,上面的类模板有两个模板参数,因此每个成员函数的定义都必须具有这两个参数。我们仍然可以像前面的例子使用这个栈,就是说只传递第一个类型实参给这个类模板,将会利用vector来管理stack的元素。使用方式如下:

1
2
3
4
5
//int栈
Stack<int> intStack;

//double栈,使用std::deque来管理元素
Stack<double,std::deque<double> > dblStack;

参考:

《C++Templates》

-------------本文结束感谢您的阅读-------------