第十六章:模板与泛型编程

第十六章:模板与泛型编程

一.定义模板

在模板定义中,模板参数不能为空。

除了定义类型参数,还可以再模板中定义非类型参数。一个非类型参数表示一个值而非一个类型。我们通过一个特定的类型名而非关键字class或typename来指定费类型参数。当一个模板被实例化时,非类型参数被一个常量表达式所代替。

一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或左值引用。绑定到非类型整型参数的实参必须是一个常量表达式,绑定到指针或引用飞行参数的实参必须具有静态的生存期或者为nullptr。

当编译器遇到一个模板定义时,它并不生成代码,只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。

为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此与非模板代码不同,模板的头文件通常既包括声明也包括定义。

类模板是用来生成类的蓝图的。与函数模板的不同之处是,**编译器不能为类模板推断模板参数类型。**我们必须在模板名后的尖括号中提供额外信息用来代替模板参数的模板实参列表。

定义在类模板之外的成员函数必须以关键字template开始,后接类模板参数列表。同样的,在类外定义一个成员时,必须说明成员属于哪个类,且类名需要包含模板形参。

默认情况下,一个类模板的成员函数只有当程序用到它时才进行实例化。

当我们使用一个类模板类型时必须提供模板实参,但在类模板自己的作用域中我们可以直接使用模板名而不提供实参。

当一个类包含一个友元声明时,类与友元各自是否是模板是相互无关的。如果一个类模板包含一个非模板友元,则友元被授权可以访问所有模板实例。如果友元自身是模板,类可以授权给所有友元模板实例(声明时必须使用与类模板本身不同的模板参数),也可以只授权给特定实例。

我们可以定义一个typedef来重命名实例化的类:typedef Blob StrBlob; 但是不能用这种方式直接给类型模板起别名,新标准允许我们使用using为类模板定义一个类型别名:template using twin = pair<T, T>。当我们定义一个类模板别名时,可以固定一个或多个模板参数:template using twin = pair<T, int>。

类模板的每个实例都有一个独有的static对象,因此我们要将static数据成员也定义为模板。一个static成员函数只有在使用时才会实例化。

模板参数遵循普通的作用域规则。一个模板参数名的可用范围是在其声明之后,至模板声明或定义结束之前。与任何其他名字一样,模板参数会隐藏外层作用域中声明的相同名字。但是,与大多数其他上下文不同,在模板内不能重用模板参数名

我们可以使用域作用符来访问类的类型或静态成员,但是对于没有确定的模板参数,我们不知道访问的是类型还是静态成员。默认情况下,C++假定通过作用域运算符访问的名字不是类型。我们可以使用关键字typename来显式告诉编译器该名字是一个类型。例如typename T::value_type。

我们可以为模板参数提供默认模板实参。与函数默认实参一样,对于一个模板参数,只有当它右侧的所有参数都有默认实参时,它才可以有默认实参。

无论何时使用一个类模板,我们都必须在模板名之后接上尖括号。如果一个类模板的所有模板参数都有默认实参且我们想使用这些默认实参,那就接一个空尖括号。

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

当我们在类模板外定义一个成员模板时,必须同时为类模板和成员模板提供模板参数列表。

当模板被使用时才会进行实例化这一特性意味着相同的示例可能出现在多个对象文件中,这个额外开销可能非常严重。在新标准中我们可以通过显式实例化来避免这种开销。显式实例化有如下形式:

extern template declaration;
template declaration;

第一个是显式实例化声明,有了这个声明表示在该文件中不会实例化,意味着在其他文件中已经有实例化的定义了。对于一个给定的实例化版本,可能有多个extern声明,但有且只有一个定义。如果要用extern声明来避免实例化,extern声明要在实例化之前。

第二个就是显式实例化的定义,如果是类模板,实例化定义会实例化该模板的所有成员,包括内联的成员函数。

通过在编译时绑定删除器,unique_ptr避免了间接调用删除器的运行时开销。通过在运行时绑定删除器,shared_ptr使用户重载删除器更为方便。

二.模板实参推断

编译器通常不是对实参进行类型转换,而是生成一个新的模板实例。将实参传递给带模板类型的函数形参时,能够自动引用的类型转换只有const转换及数组或函数到指针的转换。

一个模板类型参数可以用作欧多个函数形参的类型。由于只允许有限的几种类型转换,因此传递给这些形参的实参必须具有相同的类型。如果推断出的类型不匹配,则调用就是错误的。

如果函数参数类型不是模板参数,则对实参进行正常的类型转换。

我们可以给函数提供显式模板实参。我们提供显式模板实参的方式与定义类模板实参的方式相同。显式模板实参在尖括号中给出,位于函数名之后,实参列表之前。显式模板实参按由左至右的顺序与对应模板参数匹配。

如果我们不能提前知道函数要什么类型,就不能提供显式模板实参来初始化返回类型。这个时候我们可以用尾置返回类型加上decltype的方式,但是这种方式往往返回的是一个引用。如果不要引用。标准库还有一个类型转化模板。这些模板定义在头文件type_traits中,里面有一个模板接受一个参数返回参数相关的一个类型。

当我们用一个函数模板初始化一个函数指针或为一个函数指针赋值时,编译器使用指针的类型来推断模板实参。

一般来说我们不能创建引用的引用。如int& &这种。但是模板参数推断可以间接创造。如T&&接受一个左值时,T会被推断为X&,当出现引用的引用时,会发生引用折叠,一般来说引用折叠都是折叠为左值引用。不过当有X&& &&这种时折叠为右值引用。所以如果一个函数参数是指向模板参数类型的右值引用,如T&&,则可以传递给它任意类型的实参。如果将一个左值传递给这样的参数,则函数参数被实例化为一个普通的左值引用T&。

虽然不能隐式的将一个左值转换为右值引用,但我们可以用static_cast显式地将一个左值转换为一个右值引用。

std::move的定义如下:

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
	return static_cast<typename remove_reference<T>::type&&>(t);
}

通过引用折叠可以接受左值或右值的形参,如果是右值,则T为X,不用转换,remove_reference得到的是X。如果是左值,则T为X&,引用折叠,t为X&,转换为X&&,同时返回类型也是X&&,remove_reference得到的是X。

某些函数需要将其一个或多个实参连同类型不变地转发给其他函数。在此情况下,我们需要保持被转发实参的所有性质,包括实参是否是const以及实参是左值还是右值。

要完成转发主要通过两个步骤来实现,一是函数形参要设为Type&&,这个可以让实参到形参的const属性和左值/右值属性得到保持。二是在向其他函数转发的时候使用std::forward,std::forward(X)得到的类型是Type&&。上面这两个机制能生效的原理是引用折叠。

三.重载与模板

函数模板可以被另一个模板或一个普通非模板函数重载。与往常一样,名字相同的函数必须具有不同类型或数量的参数。

在定义任何函数之前,记得声明所有重载的函数版本,这样就不必担心编译器由于未遇到你希望调用的函数而实例化一个并非你所需的版本。

四.可变参数模板

一个可变参数模板就是一个接受可变数目的模板函数或模板类。可变数目的参数被称为参数包,存在两种参数包:模板参数包,表示零个或多个模板参数;函数参数包,表示零个或多个函数参数。

我们用一个省略号来之处一个模板参数或函数参数表示一个包。在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包。

对于一个可变参数模板,编译器会推断包中参数的数目。当我们需要知道包中有多少元素时,可以使用sizeof…运算符。

可变参数函数通常是递归的。第一步调用处理包中的第一个实参,然后用剩余实参调用自身。还要有一个不递归的非可变参数函数作终结防止无限递归。

之后有一些关于包拓展的内容,有点没看懂,之后用到了再看。

五.模板特例化

当我们不能或不希望使用模板版本时,可以定义类或函数模板的一个特例化版本。一个特例化版本就是模板的一个独立定义,在其中一个或多个模板参数被指定为特定的类型。

当我们特例化一个函数模板时,必须为原模板中的每个模板参数都提供实参。为了指出我们正在实例化一个模板,应使用关键字template后跟一个空尖括号对。空尖括号之处我们将为圆模板的所有模板参数提供共实参。

当我们定义一个特例化版本时,函数参数必须与一个先前声明的模板中对应类型匹配。

特例化的本质是实例化一个模板,而非重载它。因此,特例化的匹配在模板匹配之后,要模板匹配了才能谈要不要使用特例化的版本

为了特例化一个模板,原模板的声明必须在作用域中。而且,任何使用模板实例的代码之前,特例化版本的声明也必须在作用域中,否则可能是直接实例化了而不是使用特例化的版本。

模板及其特例化版本一个声明在同一个头文件中。所有同名模板的声明应该放在前面,然后是这些模板的特例化版本。

与函数模板不同,类模板的特例化不必为所有模板参数提供实参。我们可以指定部分而非所有模板参数,或是参数的一部分而非全部特性。一个类模板的部分特例化本身是一个模板,使用它时用户还必须为那些在特例化版本中为指明的模板参数提供实参。

对每个未完全确定类型的模板参数,在特例化版本的模板参数列表中都有一项与之对应。在类名之后,我们为要特例化的模板参数指定实参,这些实参列于模板名之后的尖括号中。这些实参与原始模板中的参数按位置对应。

我们可以只特例化特定成员函数而不是特例化整个类模板。

  • 17
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值