- 类模板是用来生成类的蓝图。与函数模板不同的是,编译器不能为类模板推断模板参数类型。因此必须在模板名后的尖括号中提供额外信息。
例如,我们将实现StrBlob的模板版本,使之不限于string版本。我们命名为Blob。
与使用标准库容器相似,使用Blob时,用户需要指出元素类型
template <typename T>
class Blob
{
public:
typedef T value_type;
typedef typename std::vector<T>::size_type size_type;
//构造函数
Blob();
Blob(std::initializer_list<T> il);
//Blob中元素数目
size_type size() const { return data->size(); }
bool empty() const { 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;
//若data[i]无效,则抛出msg
void check(size_type i, const std::string &msg) const;
};
- 当使用一个类模板时,我们必须提供额外的信息。称为显式模板实参列表。用于绑定到模板参数,使编译器以及模板实参来实例化特定的类。
Blob<int> ia; //空Blob<int>
Blob<int> ia2 = {1, 2, 4, 5}; //有4个元素的Blob<int>
等价于
template <>
class Blob<int>
{
typedef typename std::vector<int>::size_type size_type;
Blob();
Blob(std::initializer_list<int> il);
//...
int& operator[](size_type i);
private:
std::shared_ptr<std::vector<int>> data;
void check(size_type i, const std::string &msg) const;
};
##Note##
一个类模板的每个实例都形成一个对立的类。即,就算两个实例出来的Blob<int>类无关联,对对方的成员也无特殊的访问权限
- 类模板的成员函数。从一个模板生成的类的名字中必须包含其模板实参,当我们定义一个成员函数时,模板实参与模板形参要相同。
例如,首先定义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();
}
对于Blob的构造函数来说其构造函数定义为
template <typename T>
Blob<T>::Blob(std::initializer_list<T> il) : data(std::shared_ptr<vector<T>>(il)) { }
此构造函数分配一个新的vector,使用参数il来初始化
#Note#
默认情况下,对于一个已经实例化了的类模板,其成员只有在使用时才被实例化。
- 在类内部的代码中可以简化模板类型的使用。即,一个类模板的作用域内,可以直接使用模板名而不必指定模板实参。
//若始图访问一个不存在的元素,BlobPtr抛出一个异常
template<typename 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, "derefrence past end");
return (*p)[curr]; //*p为本对象指向的vector
}
//递增和递减
BlobPtr& operator++(); //在这里的返回类型并没有使用BlobPtr<T>
BlobPtr& operator--(); //在一个类模板的作用域中,编译器处理自身引用就好像已经提供了实参一样
private:
//若检查成功,check返回一个指向vector的shared_ptr
std::shared_ptr<std::vector<T>> check(std::size_t, const std::string&) const;
//保存一个weak_ptr,表示底层的vector可能被销毁
std::weak_ptr<std::vector<T>> wptr;
std::size_t curr; //数组中的当前位置
};
可以看到对递增递减的返回类型简化为BlobPtr&,其在类模板的作用域中,和下面一样
BlobPtr<T>& operator++();
BlobPtr<T>& operator--();
再看对于类的成员函数外部实现:
template<typename T>
BlobPtr<T> BlobPtr<T>::operator++(int)
{
//此时,进入了模板类内部作用域
BlobPtr ret = *this; //保存当前值
++*this; //推进一个元素;前置++会检查递增是否合法
return ret; //返回保存的状态
}
在模板类内部作用域中
BlobPtr ret = *this;
等价于
BlobPtr<T> ret = *this;
- 当模板遇上友元时,就会产生多种情况。因为当一个类包含一个友元声明时,类与友元各自是否为模板是没有关系的。即模板类可以有非模板友元,也可以有模板友元;非模板类可以有非模板友元,也可以有模板友元。(a)如果一个模板包含一个非模板友元,则友元被授权可以访问所有模板实例。(b)如果友元自身是模板,类可以授权给所有友元模板实例,也可以授权给特定实例。
一对一友好关系:类模板与另一个模板间的友好关系最常见的为建立对应实例和它的友元的关系。
例如:在模板Blob类中,应该将BlobPtr类与Blob模板的等号运算符定义为友元
!!为了引用(类或函数)模板的一个特定实例,必须先声明模板自身。声明与定义有所不同
//前置声明,在Blob中友元声明所必须的
template <typename> class BlobPtr;
template <typename> class Blob;
template <typename T>
bool operator==(const Blob<T>&, const Blob<T>&);
template <typename T>
class Blob
{
friend class BlobPtr<T>;
friend bool operator==(const Blob<T>&, const Blob<T>&);
};
首先将Blob,BlobPtr和operator==声明为模板。这些声明是operator==函数的参数声明以及Blob中友元声明所必须的。
通用和特定的模板的友元关系:
一个类也可以将另一个模板的每个实例都声明为自己的友元;或者限定特定的实例为友元。
//前置声明,在将模板的一个特例声明为友元时所必须的
template <typename T> class Pal;
class C
{//C是一个普通的非模板类
friend class Pal<C>; //用类C实例化的Pal是C的一个友元
//Pal2的所有实例都是C的友元;这种情况无需前置声明
template <typename T> friend class Pal2;
};
template <typename T> class C2
{//C2本身为一个模板
//C2的每个实例将相同实例化的Pal声明为友元
friend class Pal<T>; //Pal的模板声明必须在作用域之内
//Pal2的所有实例都是C2每个实例的友元
template <typename X> friend class Pal2;
//Pal3是一个非模板类
friend class Pal3;
};
- 模板可以将自己的类型声明为友元,用于将访问权限授予实例化后的类。
template <typename T> class Bar
{
friend T;
//...
};
此处将用来实例化Bar的类型声明为友元。因此对于Sales_data将称为Bar<Sales_data>的友元
- 模板类型的别名
通过使用typedef,这时候必须指明模板的类型,比如string
typedef Blob<string> StrBlob;
通过使用using,可以为类模板定义一个类型别名
template <typename T> using twin = pair<T, T>;
twin<string> authors; //authors为pari<string, string>类型
在定义模板时,可以固定一个或多个模板参数
template <typename T> using partNo = pair<T, unsigned>;
partNo<string> books; //book是一个pair<string, unsigned>
- 类模板的static成员。当类模板中声明有静态成员函数或成员变量时,所有实例化出来的类都会拥有该成员函数和成员变量,但是它们共享相同的静态成员函数或成员变量。同时,类似其他成员函数,一个成员函数只有在使用时才会实例化是。
- 一个特定文件所需要的所有模板的声明通常一起放置在文件开始位置,出现于任何使用这些模板的代码之前。
- 在模板代码中,当我们使用作用域运算符(::)访问成员或类型成员时,编译器只有在实例化时才懂得所要表示的含义。
例如,假设编译器遇到语句
T::size_type * p;
它需要知道是定义一个变量;还是将一个名为size_type的static数据成员与变量p相乘
当我们希望通知编译器一个名字表示类型时,必须使用关键字typename,不能使用关键字class(因为在模板定义中二者皆可使用)
template <typename T>
typename T::value_type top(const T& c)
{
if(!c.empty())
return c.back();
else
return typename T::value_type();
}
top函数期待一个容器类型的实参,它使用typename指明其返回类型并在C中没有元素时,生成一个值初始化的元素返回给调用者。
- 我们能为函数提供默认实参,也可以为模板提供默认模板实参。与函数的默认实参一样,对于一个模板参数,只有它右侧所有参数都有默认实参时,它才可以有默认实参。
//compare有个默认实参less<T>和一个默认函数实参F()
template <typename T, typename F = less<T>>
int compare(const T &n1, const T &n2, F f = F())
{
if(f(n1, n2)) return -1;
if(f(n2, n1)) return 1;
return 0;
}
为模板添加了两个类型参数,F表示可调用的类型,并默认为less<T>
bool i = compare(0, 42); //省略less默认实参,i为-1
//姐u共依赖于item1和item2中的isbn
Sales_data item1(cin), item2(cin);
bool j = compare(item1, item2, compareIsbn);
//类模板使用默认实参
template <class T = int>
class Numbers
{
public:
Numbers(T v = 0) : val(v) { }
private:
T val;
};
Numbers<long double> lots_of_precision;
Numbers<> average_precision; //空<>表示我们希望使用默认类型