第十六章 模板与泛型编程

函数模板
定义:template int compare(const T& a,const T& b) {}
以关键字template开始,后跟一个模板参数列表(不能为空),模板参数列表表示在类或函数定义中用到的类型或值,

实例化函数模板:编译器通常通过函数实参来推断欧版实参,并将它绑定到模板参数T上

模板类型参数:我们一般将模板类形参数看作类型说明符,可以用来指定返回类型和函数参数类型,以及函数体内变量的声明和类型转换

非类型模板参数:一个非类型参数表示一个值而非一个类型,通过一个特定的类型来指定,非类型参数被用户提供的一个值或编译器推断出的值代替,这个值必须是常量表达式;非类型参数也可是一个指向对象或函数的指针和引用(左值),绑定到指针或引用的实参必须有静态的生存期

inline和constexpr:放在模板参数列表后

编写类型无关的代码:

  • 模板中的函数参数是const的引用(可以用于不能拷贝的类型)
  • 条件判断仅使用<符号(降低对处理类型的要求)

模板编译:实例化出一个特定模板时,编译器才会生成代码,因此大多数编译错误在实例化时报告,模板的头文件通常既包含声明,也包含定义

类模板
定义:templateclass Blob{}
note:一个类模板的每个实例都能形成一个完全独立的类

类模板的成员函数:在类外部定义成员函数时,因为成员函数与模板有相同的模板参数,因此定义时以关键字template开始,后接类模板参数列表
template ret-type Blob::member_name(paraments-list) {}
note:默认情况下,对于一个实例化了的类模板,其成员函数只有在使用时才被实例化

类模板和友元:如果一个类模板包含一个非友元,那么该友元可以访问到所有类型的类模板;如果友元自身是模板,类模板可以授权给所有类型的友元,也可以授权给特定类型的友元

  • 一对一友好关系
  • 通用和特定的模板关系

c++11新标准:可以将模板类型参数声明为友元
template class Bar{ friend Type;}

类模板的static成员:类模板的每个实例都有一个独有的static成员,因此我们将static数据成员也定义为模板

模板参数
模板参数与作用域:一个模板参数名可用的范围是在其声明之后,至模板声明或定义结束之前;一个模板参数名在一个特定的模板参数列表中只能声明一次

使用类的类型成员:编译器直到实例化模板时才知道T::mem是一个类型成员还是一个static数据成员,默认情况下,c++语言假定通过作用域运算符(::)访问的名字不是类型。因此当我们希望使用一个模板参数类型时要显示告诉编译器,用关键字typename来实现,template typename T::valu _type foo();必须用typename而不能用class

默认模板实参:c++11标准中,我们可以为类模板和函数模板提供默认实参

模板默认实参与类模板:无论何时使用模板,我们都必须在模板名后面加上一对尖括号(<>),指出类必须从一个模板实例化而来,如果我们希望使用默认实参,就需要加上一对空的尖括号

成员模板
一个类(无论是普通类还是模板类)可以包含本身是模板的成员函数,这种函数为成员模板,成员模板不能是虚函数

当在类模板外定义一个成员模板时,必须包含类模板和成员模板的模板参数列表,类模板参数列表在前,成员模板的参数列表在后

实例化与成员模板:为了实例化一个类模板的成员模板,我们需要提供类和函数模板的实参,在哪个对象上调用成员模板,编译器就根据对象的类型来推断成员模板的实参类型。

控制实例化
模板被使用时才会实例化这一特性表明,在多个独立编译的文件中,使用了相同模板并提供了相同的实例化,这一额外开销在大系统中十分严重,所以我们用显示实例化来避免

  • 实例化声明:extern template declaration
  • 实例化定义:template declaration

当编译器遇到extern声明时,不会在本文件中生成实例化代码,而文件系统中必须有一个文件中又一个实例化的非extern声明,可以有多个extern声明,但只有一个定义

extern声明必须出现在任何使用此实例化代码之前

当编译器遇到实例化定义时,会为其生成代码

实例化定义会实例化所有成员,所用类型必须能用于模板的所有成员函数
eg:vector在显示实例化时会实例化vector的所有成员,包括设置容器大小的构造函数,该构造函数会根据元素的默认构造函数来设置,如果someType/NoDefault没有默认构造函数则不能显示实例化

  • 被调用时会实例化
  • 指针和引用不会实例化
  • 只有在被分配了空间时,模板才会被完全解析(发生在编译阶段)https://stackoverflow.com/questions/21598635/how-is-a-template-instantiated

类型转换与模板类形参数
如果一个函数模板的形参使用了模板类型参数,那么采用特殊的初始化规则,只有很有限的几种类型转换会应用于这些实参

  • const转换:将一个非const对象的引用传递给一个const的引用形参
  • 数组或函数指针转换:数组实参可以转换为指向首元素的指针,一个函数形参可以转换为一个该函数的指针
    编译器通常不对实参进行类型转换,而是生成一个新的模板实例

一个模板参数类型可以用作多个函数形参的类型,但是传递过去的实参类型必须一致

若函数模板的形参为正常参数类型,则不涉及特殊的初始化规则

函数模板显示实参
template T1 sum(T2,T3)
sum 用户显示指定T1为long

对于模板参数类型已经指定的函数实参,可以进行正常的类型转换

尾置返回类型与类型转换
尾置返回允许我们在参数列表后指明函数的返回类型
template auto fcn(T it)->decltype(*it)

我们可以使用标准库的类型转换模板来获得元素类型,这些模板在头文件type_traits中,例如remove_reference 若T为某个类型的引用,则remove_reference::type为该类型

函数指针和实参推断
当我们用函数模板来初始化一个函数指针或为函数指针赋值时,编译器通过指针的类型类推断函数模板实参
note:当参数是一个函数模板实例的地址时,程序上下文必须满足;对每个模板参数,能唯一确定其类型或值

模板实参推断和引用
从左值引用函数参数推断类型:如果模板函数参数是T&(左值引用),那么我们的实参只能为左值(一个变量或一个返回引用类型的表达式),实参可以使const,也可以不是;如果一个模板函数参数是const T&,我们可以传递给它任何类型的实参,当函数参数本身是const时,T的推断类型不会是const

右值引用函数参数的推断过程类似左值引用

引用折叠和右值引用参数
通常情况下,不能将一个右值引用绑定到左值上,但是有两个例外的规则

  • 在函数模板参数上,将一个左值传递给函数的右值引用参数,且次右值引用指向模板参数(T&&),则编译器推断T为实参左值引用类型(如int&)
  • 我们间接创建的引用的引用,则这些引用会折叠,折叠成一个普通的左值引用,只有在右值引用的右值引用下才会折叠成右值引用
  • X& &、X& &&、X&& &折叠成X&
  • X&& &&折叠成X&&
  • 引用折叠只应用于间接创建引用的引用,如类型别名和模板参数

note:如果一个函数参数是指向模板参数类型的右值引用(T&&),则可以传递给模板任何类型的实参,如果将左值传递给这样的参数,则函数参数被实例化为一个左值引用

右值引用通常应用于:模板转发与模板重载

eg模板参数类型推断

template void func1(const T&)
template void func2(T&)
template void func3(T&&)
template void func4(T)

main()
{
       int i=0;
       const int ci=i;
       func1(i);     T为int
       func1(ci);    T为int
       func1(42);    T为int

       func2(i);     T为int
       func2(ci);    T为int
       func2(42);    错误,42不是左值

       func3(i);     T为int&(T推断为int& &&折叠成int&)
       func3(ci);    T为const int&
       func3(42);    T为int

       func4(i);     T为int
       func4(ci);    T为int
       func4(42);    T为int
}

std::move
标准库函数std::move是使用右值引用参数的一个很好的例子,std::move可以让我们将一个右值引用绑定到左值上,move可以接受任何类型的实参

note:从一个左值static_cast到一个右值引用是允许的

转发
某些函数需要将一个或多个不同的实参地转发给其它函数,我们需要保持被转发参数的所有性质,不论是const还是左值右值属性

  • 我们可以将函数参数定义为一个指向模板的右值引用,可以保持其对应实参的所有信息(底层const)(不能用于接受右值引用的函数)
  • 对于接受右值引用的函数,我们可以使用标准库函数std::forward来保持其所有属性

eg:

#include 
using namespace std;
void f(int v1, int& v2)
{
	cout << v1 << " " << ++v2 << endl;
}
void g(int&& v1, int& v2)    //接受右值引用的函数
{
	cout << v1 << " " << v2 << endl;
}
template void flicp1(F f, T t1, U t2)
{
	f(t2, t1);
}
template void flicp2(F f, T&& t1, U&& t2)
{
	f(t2, t1);
}
template void flicp3(F f, T&& t1, U&& t2)
{
    g(t2,t1)           //错误,不能使用左值实例化int&&
     
	g(std::forward(t1), std::forward(t2));  
	                      //错误,std::forward(t1)返回的是int&& &类型,折叠成int&

	g(std::forward(t2), std::forward(t1));  
	                      //std::forward(t2)返回int&&(因为t2是普通的int)
}
int main(int argc, char** argv)
{
	int i = 0;
	flicp1(f, i, 42);
	cout << "i=" << i << endl;    //没有改变实参i的值

	flicp2(f, i, 42);
	cout << "i=" << i << endl;    //改变了实参i的值

	flicp3(g, i, 42);             
	system("pause");
}

重载与模板

  1. 匹配的函数中只有一个是非模板函数,选择它
  2. 匹配的函数中只有模板函数,那么选择那个最特例的

你可能感兴趣的:(第十六章,模板与泛型编程,c++primer,5th)