类模板是用来生成类的蓝图的,与模板函数不同之处在于。编译器不能为模板推断模板的参数类型,如我们多次已经看到的,为了使用类模板,我们必须在模板名后的尖括号中提供额外信息,用来代替模板参数的模板实参列表
作为一个例子,我们将实现一个类 StrBlob的模板版本,我们将此模板命名为Blob。类似函数模板,类模板以关键字template开始,后跟模板参数列表,在类模板的定义中,我们将模板参数当做替身,代替使用模板时用户需要提供的类型或值。
template<typename T>
class Blob
{
public:
typedef T value_type;
typedef typename std::vector ::size_type size_type;
//构造函数
Blob();
Blob(std::initializer_list il);
//Blob中的元素数目
size_type size() const {return data->size();}
bool empty()const { return data->empty(); }
void push_back(const T&& t) {data->push_back(std::move(t)); }
void push_back(const T& t) {data->push_back(t); }
private:
std::shared_ptr<std::vector > data;
void check(size_type i, const std::string& msg) const;
}
我们的模板有一个名为T的模板类型参数,用来表示Blob保存的元素的类型,例如,我们将元素访问操作返回类型定义为T&。用户实例化Blob时,T就会被替换为特定的模板实参类型。
我们已经多次见到,当使用一个类模板时,我们必须提供额外的信息,我们现在知道这些额外信息就是显式模板实参列表,他们被绑定到模板参数,编译器使用这些模板实参来实例化出特定的类。
例如:
Blob<int> ia;
Blob<int> ia2 = {0, 1, 2, 3, 4};
ia和ia2 会使用相同的特定类型版本的Blob,根据这两个定义,编译器会实例化出一个与下面定义等价的类:
template<>
class Blob<int>
{
public:
typedef typename std::vector<int>::size_type size_type;
Blob();
Blob(std::initializet_list<int> il);
//...
private:
std::shared_ptr<std::vector<int>> data;
}
对我们指定的每一种元素类型,编译器都生成不同的一个类,一个类模板的每个实例都形成一个独立的类,类型Blob与任何其他Blob类型都没有关联,也不会对其他任何Blob类型的成员有特殊的访问权限。
为了阅读模板类代码,应该记住类模板的名字不是一个类型名,而是用来实例化类型,而一个实例化的类型总是包含模板参数的。可能令人迷惑的是 一个类模板中的代码如果使用了另一个模板,通常不将一个实际类型(或值)的名字用作模板实参数,相反的,我们通常将模板自己的参数当做被使用的模板的实参,例如,我们的data成员使用了两个模板,vector和shared_ptr。我们知道,无论何时使用模板都必须提供模板实参,在本例中,我们提供的模板实参就是Blob的模板参数,因此data的定义如下:
std::shared_ptr<std::vector > data;
它使用了Blob的类型参数来声明data是一个shared_ptr的实例,此shared_ptr指向一个保存类型为T的对象的vector实例,当我们实例化一个特定类型的Blob,例如Blob时。data会成为:
shared_prt<vector<string>>
如果我们实例化Blob,则data会成为shared_ptr
与其他类相同,我们既可以在类模板内部,也可以在类模板外部定义为其定义成员函数,且定义在类模板内的成员函数被隐式声明为内联函数,类模板的成员函数本身是一个成员函数,但是类模板的每个实例都有其自己版本的成员函数,因此,类模板的成员函数具有和模板相同的模板参数,因而定义在模板之外的成员函数就必须以关键字template开始,后接类模板参数列表:
template<typename T>
ret-type Blob<T>::member-name(parm-list)
如上例中的check:
templateT>
void Blob<T>::check(size_type i, const std::string& msg) const
{
//...
}
除了类名中的不同之处以及使用了模板参数列表外,此函数与元StrBlob类的check成员函数函数一模一样
与其他任何定义在模板外的成员一样,构造函数的定义要以模板参数开始
template<typename T>
Blob::Blob():data(std::make_shared<std::vector >()) {}
这段代码在作用域Blob中定义了名为Blob的成员函数,类似StrBlob的默认构造函数,此构造函数分配一个空vector,我们将类模板自己的类型参数作为vector的模板实参来分配vector
类似的,接受一个initializer_list 参数的构造函数将为其类型参数T作为initializer_list参数的元素类型
template<typename T>
Blob::Blob(std::initializer_list il):
data(std::make_shared<std::vector >(il)){}
类似默认构造函数,此构造函数分配一个新的vector,并使用参数il初始化本vector,为使用这个构造函数,我们必须传递给它一个initializer_list,其中的元素必须与Blob的元素类型兼容
Blob articles = {"a", "an", "the"};
//构造函数参数类型为std::initializer_list ,列表中的每个字符串字面常量隐式转化为一个string
默认情况下,一个类模板成员只有当程序用到它时才进行实例化,如果一个成员函数不被使用,就不会被实例化。因此衍生出模板的分离编译问题,后续再谈。