类模板
- 类模板是用来生成类的蓝图
- 与函数模板不同的是,编译器能为类模板推断模板参数类型,所以在使用类模板时,需要在模板名后的尖括号里提供额外的信息,用来补充类模板的实参
- 类似函数模板,类模板以关键字template开始,后跟模板参数列表,在类模板或者其成员的定义中,用模板参数当作替身
template<typename T> class Blob{
public:
typedef T value_type;
typedef typename std::vector<T>::size_type size_type;
Blob();
size_type size() const {return data->size();}
bool empty() {return data->empty();}
void push_back(const T &t) {data->push_back(t);}
void push_back(T &&t) {data->push_back(std::move(t));}
void pop_back();
T& back();
T& operator[](size_type i);
private:
std::shared_ptr<std::vector<T>> data;//使用shared_ptr和vector模板
void check(size_type i, const std::string &msg) const;
}
实例化类模板
- 当使用一个类模板时,必须提供额外的信息,这些信息是显示模板实参,他们被绑定到模板参数中,编译器利用这些模板实参来实例化特定的类
- 编译器从一个类模板实例化出一个类时,会用模板实参代替模板参数对应的类型或者值
- 对指定的每种类型,编译器生成一个不同的类
- 一个类模板的每个实例都形成一个独立的类
在模板作用域中引用模板类型
- 类模板的名字不是一个类型名
- 类模板用来实例化类型,而一个实例化的类型总是包括模板参数的
- 一个类模板中的代码使用另外一个模板
类模板的成员函数
- 可以在类模板内部也可以类模板外部定义成员函数,定义在模板内的成员函数被隐式声明为内联函数
- 定义在模板外的成员函数必须以关键字template开始,后接类模板参数列表
- 类模板的每个实例都有自己版本的成员函数
- 从一个模板生成的类的名字中必须包括其模板实参
- 当定义一个成员函数时,模板实参与模板形参相同
ret-type StrBlob::menber-name(parm-list)
//为Blob定义check函数
template<typename T>
void Blob<T>::check(size_type i, const std::string &msg)const{
if(i >= data->size())
throw std::out_of_range(msg);
}
template<typename T>
T& Blob<T>::back()
{
check(0,"back on empty Blob");
return data->back();
}
template<typename T>
T& Blob<T>::operator[](size_type i)
{
check(i,"subscript out of range");
return(*data)[i];
}
template<typename T>
Blob<T>::pop_back()
{
check(0,"pop_back on empty Blob");
return data->pop_back();
}
类模板成员的实例化
- 默认情况下,一个类模板的成员函数只有当程序用到他时才进行实例化,这一特性使得即使某种类型不能完全符合模板操作的要求,我们仍然能用该类型实例化类
在类代码内简化模板类名的使用
- 当处于一个模板的作用域时,编译器处理模板自身引用时,就好象我们已经提供了与模板参数匹配的实参
- 在类模板自己的作用域中,我们可以直接使用模板名而不提供实参
template<T> class BlobPtr{
public:
BlobPtr():curr(0){}
BlobPtr(Blob<T> &a, size_t sz = 0):
wptr(a.data),curr(sz){}
T& operator*()const
{auto p = check(curr,"dereference past end");
return (*p)[curr];
}
BlobPtr& operator++();//在自己的作用域内,直接使用模板名
BlobPtr& operator--();
private:
std::shared_ptr<std::vector<T>>
check(std::size_t,const std::string&)const;
std::weak_ptr<std::vector<T>> wptr;
std::size_t curr;
}
在类模板作用域外
- 在类模板作用域外必须提供模板参数
- 知道遇到类名才算进入类的作用域
template<typename T>
BlobPtr<T> BlobPtr<T>::operator++(int){
BlobPtr ret = *this;
++*this;
return ret;
}
类模板与友元
- 如果类模板包括一个非模板友元,那么该友元被授权可以访问所有模板实例
- 如果友元自身是模板,类可以授权给所有友元模板实例,也可以只授权给特定实例
一对一友好关系
- 如果用类模板中的形参作为其友元类或者函数模板的实参,那么友好关系被限定在相同类型的实例之间
//前置声明
template<typename> class BlobPtr;
template<typename> class Blob;
template<typename>
bool operator==(const Blob<T>&,cosnt Blob<T>&);
template<T> Class Blob{
friend class BlobPtr<T> ;
friend bool operator==<T>;
(const Blob<T>&,cosnt Blob<T>&);
}
通用和特定的模板友好关系
- 一个类可以将另一个模板的每个实例都声明为自己的友元,或者限定特定的实例为友元:
template<typename T> Class pal;//前置声明
class C{ //C本身是一个类,不是模板
friend class pal<C>;//用类C实例的pal是C的一个友元
template<typename T>friend class pal2 ;//pal2的每个实例都是C的友元
}
*为了让所有实例成为友元,友元生命中必须用与类模板本身不同的模板参数
template<Ttypename T> class C2{ //C2是模板
friend class pal<T>;//C2的每个实例都将相同类型实例的pal视为友元
template<typename U>friend class pal2 ;//pal2的所有实例是C2每个实例的友元
friend class pal3;//pal3是非模板类,pal3是每个C2实例的友元
}
令模板自己的类型参数成为友元
template<typename T> class Bar{
friend T;
}
- T可以为内置类型
模板类型别名
- 定义一个typedef来引用实例化的类
typedef Blob<string> StrBlob;
- 为类模板定义一个类型别名
template<T> using twin = pair<T,T>;
twin<string> author;
- 定义一个模板类型别名时,固定一个或多个参数
template<T> using twin = pair<T,unsigned>;
类模板的static成员
- 每个类模板的实例都有自己的static成员实例,同一个类型的实例共享相同的static对象
- 模板类的static成员有且只有一个定义
- 类模板的每个实例都有一个独有的static对象,我们将static数据成员也定义为模板
template<T> size_t Foo<T>::ctr = 0;
- 通过类类型对像访问一个类模板的static成员,也可以通过作用域运算符直接访问
- 通过类来直接访问static成员,必须引用一个特定的实例
习题
- 函数模板是函数的蓝图,可以把逻辑功能相同而函数参数和返回值类型不同的多个重载函数用一个函数来描述。参数化的函数称为函数模板,代表的是一个函数家族。
- 类模板是类的蓝图,是对一批仅仅成员数据类型不同的类的抽像
- 类模板被实例化时,编译器用实参代替类中的形参来重写类