这一章是《C++ Templates》第三章的读书笔记。对类模板做了简单的入门介绍。相对来说比较简单。主要内容有:
下面是书中的源代码:
1:对模板来说声明和定义应该在同一文件。书中说明后面会介绍如何放在不同文件。但在这里,如果把声明和定义分开放在.h和.cpp文件中就不能通过编译。
2:对于上面的例子,模板类的类型应该是Stack<T>,其中T是实例化该模板类时的参数。因此,在成员函数的实现前面应该写作Stack<T>::Push(...)之类的形式。
但是,类名还是Stack,也就是说在构造和析够函数还是应该是Stack<T>::Stack()和Stack<T>::~Stack()。注意,前面还是应该有Stack<T>作为类型定义。
3:注意到其中的pop函数,不论是参数憾事返回值,它都没有用到类模板的参数<T>。但在实现的时候还是必须在前面加上template<typename T>。主要是因为pop的类型限定Stack<T>中用到了类模板的参数T。
下面的代码显示了类模板的使用方法:
二:类模板的特化。
在《C++ Templates》中,类模板的特化被描述为一个和重载类似的概念。我的理解是,特化允许我们对某些特殊的参数(这里就是类型)进行特殊的处理。特化的处理都是在类名后面做文章的。特化分为全局特化和局部特化。对于全局特化,书中的例子是希望对于Stack<T>模板,如果参数为std::string的类型,就用deque作为容器来处理,而其他的保持不变。因此,我们需要对Stack<T>模板作std::string的特化处理。代码如下:
但是,我认为特征化和重载还是有区别的。试想有一个函数Func(int, int),另外一个函数对它进行重载为Func(string, string)。在实际上我们也可以说int的Func重载了string的Func,这是相互的。但是特化却不能这么说。因为特化是对某种类型的特殊处理,我们可以说特化模板重载了某个模板,但是不能说某个模板重载了特化的模板。这是单方向的。另外,如果,我们不需要Func(int, int)函数,我们完全可以把它删去。但是特化模板不能离开它依赖的类模板单独存在。在上面的例子中,如果删除Stack.h文件,StringStack.h文件的定义就会出错。
StringStack是Stack模板的特化。但是他们之间的联系其实不是那么紧密,除了名字上以外。例如,Stack模板中的成员函数不必非得在StringStack中出现;同理,StringStack中的函数也不必是Stack中的函数。也就是说,特化的模板类可以根据自己的需要完全重写指定的模板函数,也可以弃原来模板函数中的成员不用,另外定义成员函数。这方面没有限制。
在理解了全局的特化以后,在来看局部的特化就很容易明白了。局部特化是要求在指定的条件下使用指定的类模板的重载版本。具体可以参照书中的内容。
三:缺省的模板实参。
和普通函数的缺省参数一样,类模板也可以使用缺省的类型参数。书中举了一个类似的例子。这里回想起函数模板的时候,没有提到缺省参数的问题。这要从函数模板实例化时参数的推导说起。函数模板实例化时的参数是根据调用函数的参数类型来推导的。而在C/C++中,所有的变量都是确定类型的,调用一个函数时都有确定的类型。所以就没有必要设置缺省参数了。
这章似乎只是类模板的一个简单介绍。相信后面还有更深入的内容。
- 类模板的声明,定义和使用,以及成员函数的实现。
- 类模板的特化。
- 缺省的类模板参数。
下面是书中的源代码:
#pragma once
#include <vector>
#include <stdexcept>
template<typename T>
class Stack
{
private:
std::vector<T> elems;
public:
Stack(void);
void push(T const&);
T top() const;
void pop();
bool empty() const;
~Stack(void);
};
template<typename T>
Stack<T>::Stack(void)
{
}
template<typename T>
void Stack<T>::push(T const& elem)
{
elems.push_back(elem);
}
template<typename T>
T Stack<T>::top() const
{
if(elems.empty())
{
throw std::out_of_range("Stack<>::top()==> empty stack.");
}
return elems.back();
}
template<typename T>
void Stack<T>::pop()
{
if(elems.empty())
{
throw std::out_of_range("Stack<>::pop()==> empty stack.");
}
elems.pop_back();
}
template<typename T>
bool Stack<T>::empty()const
{
return elems.empty();
}
template<typename T>
Stack<T>::~Stack(void)
{
}
需要注意:#include <vector>
#include <stdexcept>
template<typename T>
class Stack
{
private:
std::vector<T> elems;
public:
Stack(void);
void push(T const&);
T top() const;
void pop();
bool empty() const;
~Stack(void);
};
template<typename T>
Stack<T>::Stack(void)
{
}
template<typename T>
void Stack<T>::push(T const& elem)
{
elems.push_back(elem);
}
template<typename T>
T Stack<T>::top() const
{
if(elems.empty())
{
throw std::out_of_range("Stack<>::top()==> empty stack.");
}
return elems.back();
}
template<typename T>
void Stack<T>::pop()
{
if(elems.empty())
{
throw std::out_of_range("Stack<>::pop()==> empty stack.");
}
elems.pop_back();
}
template<typename T>
bool Stack<T>::empty()const
{
return elems.empty();
}
template<typename T>
Stack<T>::~Stack(void)
{
}
1:对模板来说声明和定义应该在同一文件。书中说明后面会介绍如何放在不同文件。但在这里,如果把声明和定义分开放在.h和.cpp文件中就不能通过编译。
2:对于上面的例子,模板类的类型应该是Stack<T>,其中T是实例化该模板类时的参数。因此,在成员函数的实现前面应该写作Stack<T>::Push(...)之类的形式。
但是,类名还是Stack,也就是说在构造和析够函数还是应该是Stack<T>::Stack()和Stack<T>::~Stack()。注意,前面还是应该有Stack<T>作为类型定义。
3:注意到其中的pop函数,不论是参数憾事返回值,它都没有用到类模板的参数<T>。但在实现的时候还是必须在前面加上template<typename T>。主要是因为pop的类型限定Stack<T>中用到了类模板的参数T。
下面的代码显示了类模板的使用方法:
#include "stdafx.h"
#include "Stack.h"
#include <iostream>
#include <string>
#include <cstdlib>
#include <stdexcept>
int _tmain(int argc, _TCHAR* argv[])
{
try
{
Stack<int> intStack;
Stack<std::string> stringStack;
intStack.push(7);
std::cout<<"intStack.top() = >"<<intStack.top()<<std::endl;
stringStack.push("Hello!");
std::cout<<"stringStack.top() = >"<<stringStack.top()<<std::endl;
stringStack.pop();
stringStack.pop();
}
catch(std::exception const& ex)
{
std::cerr<<"Exception: "<<ex.what()<<std::endl;
return EXIT_FAILURE;
}
return 0;
}
其中,语句[Stack<int> intStack;]展示了类模板的实例化,并定义一个变量的过程。和函数模板的实例化一样,类模板的实例化也必须提供所需参数的类型。实例化后定义的变量可以和普通变量一样的调用类模板定义的成员函数。只是需要注意,对于类模板的成员函数,只有在被调用的时候才会被实例化。对于上面的intStack,由于只调用了push()成员函数,所以它只实例化了push成员函数(构造和析够函数除外,他们会被默认的调用)。这样作有两个好处:一是可以节约空间和时间。二是,对于每一个类模板的参数类型,都要求提供模板所需要的操作。比如,如果你用自定义的类MyClass作为一个类模板Caculator<T>的参数。由于Caculator类模板要求提供的参数类型支持“+”和"-"操作。但是,你的MyClass类只需要用到“+”操作,没有提供"-"操作。得益于上面的规则,你的MyClass类型还是可以作为Caulator<T>的参数。前提是你没有用到"-"相关的成员函数。#include "Stack.h"
#include <iostream>
#include <string>
#include <cstdlib>
#include <stdexcept>
int _tmain(int argc, _TCHAR* argv[])
{
try
{
Stack<int> intStack;
Stack<std::string> stringStack;
intStack.push(7);
std::cout<<"intStack.top() = >"<<intStack.top()<<std::endl;
stringStack.push("Hello!");
std::cout<<"stringStack.top() = >"<<stringStack.top()<<std::endl;
stringStack.pop();
stringStack.pop();
}
catch(std::exception const& ex)
{
std::cerr<<"Exception: "<<ex.what()<<std::endl;
return EXIT_FAILURE;
}
return 0;
}
二:类模板的特化。
在《C++ Templates》中,类模板的特化被描述为一个和重载类似的概念。我的理解是,特化允许我们对某些特殊的参数(这里就是类型)进行特殊的处理。特化的处理都是在类名后面做文章的。特化分为全局特化和局部特化。对于全局特化,书中的例子是希望对于Stack<T>模板,如果参数为std::string的类型,就用deque作为容器来处理,而其他的保持不变。因此,我们需要对Stack<T>模板作std::string的特化处理。代码如下:
#include <deque>
#include <string>
#include <stdexcept>
#include "Stack.h"
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();
}
注意到特化类模板的定义和普通的类模板完全不一样了。主要区别有:#include <string>
#include <stdexcept>
#include "Stack.h"
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();
}
- 特化类模板的前面加上了template<>,没有指定参数。而是在类名后面指定了类型参数。
- 在函数的定义里面,原来的类型T全部换成了特化的类型std::string。实际上,完全可以根据特殊需要重写成员函数。甚至可以定义另外的函数。
但是,我认为特征化和重载还是有区别的。试想有一个函数Func(int, int),另外一个函数对它进行重载为Func(string, string)。在实际上我们也可以说int的Func重载了string的Func,这是相互的。但是特化却不能这么说。因为特化是对某种类型的特殊处理,我们可以说特化模板重载了某个模板,但是不能说某个模板重载了特化的模板。这是单方向的。另外,如果,我们不需要Func(int, int)函数,我们完全可以把它删去。但是特化模板不能离开它依赖的类模板单独存在。在上面的例子中,如果删除Stack.h文件,StringStack.h文件的定义就会出错。
StringStack是Stack模板的特化。但是他们之间的联系其实不是那么紧密,除了名字上以外。例如,Stack模板中的成员函数不必非得在StringStack中出现;同理,StringStack中的函数也不必是Stack中的函数。也就是说,特化的模板类可以根据自己的需要完全重写指定的模板函数,也可以弃原来模板函数中的成员不用,另外定义成员函数。这方面没有限制。
在理解了全局的特化以后,在来看局部的特化就很容易明白了。局部特化是要求在指定的条件下使用指定的类模板的重载版本。具体可以参照书中的内容。
三:缺省的模板实参。
和普通函数的缺省参数一样,类模板也可以使用缺省的类型参数。书中举了一个类似的例子。这里回想起函数模板的时候,没有提到缺省参数的问题。这要从函数模板实例化时参数的推导说起。函数模板实例化时的参数是根据调用函数的参数类型来推导的。而在C/C++中,所有的变量都是确定类型的,调用一个函数时都有确定的类型。所以就没有必要设置缺省参数了。
这章似乎只是类模板的一个简单介绍。相信后面还有更深入的内容。