一、模板与泛型编程的不同之处:
OOP能处理类型在程序之前都未知的情况;而在泛型编程中,在编译时就能获知类型了。
C++语言联邦包括四大部分:C、Object-Oriented C++、Template C++、STL;由此可见泛型编程是非常重要的一部分,应当重点对待;当我们编写一个泛型程序时,是独立于任何特定类型来编写的。模板是泛型编程的基础,一个模板就是一个创建类或函数的公式。
二、定义模板:
1 template <typename T> 2 int compare(const T& v1, const T& v2) { 3 if(v1 < v2) return -1; 4 if(v2 < v1) return 1; 5 return 0; 6 }
模板定义以关键字template开始,后跟一个模板参数列表(template parameter list),它是一个逗号分隔的一个或多个模板参数(parameter list)的列表,用<和>包围起来。模板参数列表不能为空。
模板参数表示在类或函数定义中用到的类型或值。当使用模板时,我们(隐式或显示)指定模板实参(template argument),将其绑定到模板参数上。
三、实例化函数模板:
编译器用推断出的模板参数来为我们实例化(instantiate)一个特定版本的函数。当编译器实例化一个模板时,它使用实际的模板实参来替代对应的模板参数来创建出模板的一个新“实例”
对于第一个调用,编译器会编写并编译一个compare版本,其中T被替换为int,对于第二个版本,T被替换为vector<int>.这些编译器生成的模板通常被称为模板的实例。(instantiation)
3.1 模板类型参数
我们可以将模板类型参数看作是类型说明符,模板类型参数可以用来指定返回类型或函数的参数类型,以及在函数体内用于变量声明或类型转换。
3.2 非类型模板参数
一个非类型参数表示一个值而非一个类型,当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所替代。这些值必须是常量表达式,从而允许编译器在编译时实例化模板。
template<unsigned N, unsigned M> int compare(const char (&p1)[N], const char (&p2)[M]) { return strcmp(p1, p2); } //当我们call compare时: compare("hi", "mom") //编译器会使用字面值常量的大小来代替N和M,从而实例化模板。并且编译器会在一个字符串字面值常量的末尾插入一个空字符串作为终结符。 int compare(const char (&p1)[3], const char (&p2)[4])
一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或(左值)引用。绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用类型的参数的实参必须具有静态的生存期。(如果某一个对象的生存期和程序的运行的生存期一样,则这个对象具有静态生存期,static).指针参数也可以用nullptr或一个值为0的常量表达式来实例化。
非类型模板参数的模板实参必须是常量表达式。
3.3 inline和constexpr的函数模板
inline或constexpr说明符放在模板参数列表之后,返回类型之前。
template <typename T> inline T min(const T&, const T&);
3.4 编写类型无关的代码(类型无关和可移植性)
template <typename T> int compare(const T &v1, const T &v2) { if (less<T>()(v1, v2)) return -1; if (less<T>()(v2, v1)) return 1; return 0; }
我们的compare函数通过将参数指定为const的引用,且使用less来替代>或<,这使得我们的程序具有较好的移植性。模板程序应该尽量减少对实参类型的要求。
3.5 模板编译
当编译器遇到一个模板定义时,它并不会生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。 模板为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。
因此模板的头文件通常既包括声明也包括定义。
第一阶段是编译模板本身时(语法错误)。第二阶段是编译器遇到模板使用时(实参数目是否正确,参数类型是否匹配)。第三阶段是模板实例化时(这个阶段才能发现类型相关的错误)
保证传递给模板的实参支持模板所要求的操作,以及这些操作在模板中能正确工作,是调用者的责任。
四、类模板:
编译器不能为类模板推断模板参数类型。为了使用类模板,我们必须在模板名后的尖括号中提供额外信息——用于代替模板参数的模板实参列表。
1 template <typename T> class Blob { 2 public: 3 typedef T value_type; 4 typedef typename std::vector<T>::size_type size_type; 5 // constructors 6 Blob(); 7 Blob(std::initializer_list<T> il); 8 // number of elements in the Blob 9 size_type size() const { return data->size(); } 10 bool empty() const { return data->empty(); } 11 // add and remove elements 12 void push_back(const T &t) {data->push_back(t);} 13 // move version; see § 13.6.3 (p. 548) 14 void push_back(T &&t) { data->push_back(std::move(t)); } 15 void pop_back(); 16 // element access 17 T& back(); 18 T& operator[](size_type i); // defined in § 14.5 (p. 566) 19 private: 20 std::shared_ptr<std::vector<T>> data; 21 // throws msg if data[i] isn't valid 22 void check(size_type i, const std::string &msg) const; 23 };
将模板参数作为替身,替代使用模板时用户需要提供的类型或值。
4.1 实例化类模板
当使用一个类模板时,我们必须提供显式模板实参(explicit template argument)列表,它们被绑定到模板参数。编译器使用这些模板实参来实例化出特定的类。
Blob<int> ia; // empty Blob<int> Blob<int> ia2 = {0,1,2,3,4}; // Blob<int> with five elements
当编译器从我们的Blob模板实例化出一个类时,它会重写Blob模板,将模板参数T的每个实例替换为给定的模板实参,在本例中是int。
对我们指定的每一种元素类型,编译器都会生成一个不同的类:
Blob<string> names; // Blob that holds strings Blob<double> prices;// different element type
一个类模板的每个实例都形成一个独立的类。类型Blob<string>与任何其他Blob类型都没有关联,也不会对任何其他Blob类型的成员有特殊访问权限。
4.2 在模板作用域中引用模板类型
std::shared_ptr<std::vector<T>> data; //当我们实例化一个特定类型的Blob时,data会变成: shared_ptr<vector<string>>
4.3 类模板的成员函数
类模板的每个实例都有其自己版本的成员函数,因此,类模板的成员函数具有和模板相同的模板参数。因而定义在类模板之外的成员函数就必须以关键字template开始,后接模板参数列表。
其成员函数类型如下所示:
1 template <typename T> 2 ret-type Blob<T>::member-name(parm-list)
可以看出在类模板中,除了使用了模板参数列表之外,与类模板类的成员函数基本相同。
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) { // if i is too big, check will throw, preventing access to a nonexistent element check(i, "subscript out of range"); return (*data)[i]; }
构造函数如下:
template <typename T>
Blob<T>::Blob(): data(std::make_shared<std::vector<T>>()) { }
构造函数2
template <typename T> Blob<T>::Blob(std::initializer_list<T> il):
data(std::make_shared<std::vector<T>>(il)) { }
当有如下实例化时:
Blob<string> articles = {"a", "an", "the"};
则列表中的每个字符串常量隐式的转换为一个string
4.4 类模板成员函数的实例化:
4.5 在类代码中简化模板类名的使用
BlobPtr& operator++(); // prefix operators BlobPtr& operator--();
//上述表达等同于下述表达 BlobPtr<T>& operator++(); BlobPtr<T>& operator--();
4.6 类模板外使用类模板名
template <typename T> BlobPtr<T> BlobPtr<T>::operator++(int) //返回类型位于类的作用域之外,直到遇到类名才进入类的作用域内 { // no check needed here; the call to prefix increment will do the check BlobPtr ret = *this; // save the current value 所以在定义ret时与 BlobPtr<T> ret = *this是等价的 ++*this; // advance one element; prefix ++ checks the increment return ret; // return the saved state }
4.7 类模板与友元
当一个类包含一个友元声明时,类与友元各自是否是模板是相互无关的。
4.8 一对一友好关系
类模板与另一个(类或函数)模板间建立友好关系的常见形式是:建立对应实例及友元间的友好关系。
//前置声明,在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 { // each instantiation of Blob grants access to the version of // BlobPtr and the equality operator instantiated with the same type friend class BlobPtr<T>; friend bool operator==<T> (const Blob<T>&, const Blob<T>&); };
我们首先将Blob、BlobPtr和operator==声明为模板。这些声明是operator==函数的参数声明以及Blob的友元声明所需要的。友元的声明用Blob的模板形参作为它们自己的模板实参,这些声明是operator==函数的参数声明以及Blob的友元声明所需要的。
模板特例化
模板偏特化 : 范围的偏特化(T*)特化为一个指针,但是T的类型又是泛化的、个数的偏特化(两个模板参数中有一个被特化)