🥬函数模板
2022-6-15
| 2023-8-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property

 
 
为每个类型都定义相同的操作是十分繁琐的,而且需要知道所有操作的类型,更合理的方式是定义函数模板,然后在使用时提供类型:
对每种希望比较的类型,重复定义完全一样的函数体, 是非常烦琐且容易出错的。而且,需要确定可能要compare的所有类型,如果希望能在用户提供的类型上使用此函数, 这种策略可能就失效了。
 
相比于为每个类型定义一个新的函数,可以定义函数模板(function template)。
模板定义从关键字 template 开始,后面跟着模板参数列表(在模板定义中,模板参数列表不能是空的)——放在尖括号中的逗号分隔的一个或多个模板参数
 
模板参数列表函数参数列表类似,模板参数表示在类或函数定义中使用到类型或值。当使用模板时,通过隐式或显式的方式将模板实参绑定到模板参数。
 
 

函数模板实例化

调用函数模板时,编译器使用实参的类型来决定绑定到模板参数 T 上的类型。通常函数模板是通过推断得到的,不需要显式提供,而且可能推断出来的类型与调用实参的类型并不一样。
这里实参类型是int,编译器将int 推断为模板实参,并且将此实参绑定到模板参数T上。
 
编译器使用推断的模板参数来实例化一个特定版本的函数。当编译器实例化一个模板时,乃是使用模板实参替换对应的模板参数来创建一个模板的新“实例”:
这些编译器生成的函数被统称为模板的实例
 

模板类型参数

compare 函数具有一个模板类型参数,通常,可以将类型参数当作一个类型说明符(type specifier)来使用,这与使用内置类型或类类型是一样的。特别是,类型参数可以被使用于返回类型或函数参数类型,或者变量声明以及强转时的类型。
 
每个类型参数都可以被放在关键字 class typename 之后:
classtypename 在这种情况下是完全一样的,且可以互换。而使用 typname 关键字是一个更加直观的方式,毕竟可以使用内置类型作为模板类型实参,而且 typename 更加清晰地告知后面跟随的名字是类型名字。然而,当typename 被添加到 C++ ,模板已经被广泛运用了;一些程序员依然在继续使用 class 关键字。
 
 

非类型模板参数

除了可以定义类型参数,还是可以定义带有非类型参数的模板。非类型参数表示一个值而不是类型。非类型参数通过使用特定类型名字而不是 classtypename 来指定。当模板被实例化时,非类型参数将被一个用户提供的值或者编译器推断的值替代。这些值必须是常量表达式,只有这样编译器才能在编译期间实例化模板:
编译器将会使用字面量的长度替换 NM 非类型参数来实例一个模板版本,上面将被实例化为:
一个非类型参数可能是整数类型、函数或对象的指针或左值引用。绑定到整型的实参必须是常量表达式;绑定到指针或引用的实参必须具有静态生命周期,不能使用常规本地对象或动态对象的地址或引用作为实参,指针参数还可以被实例化为 nullptr 或零值常量表达式;
模板的非类型参数在模板定义中是一个常量值。一个非类型参数可以在需要常量表达式的时候使用,如:作为数组的长度。
提供给非类型模板参数的模板实参必须是常量表达式。
 
 

内联和constexpr 函数模板

函数模板可以与常规函数一样被声明为inlineconstexpr的,inlineconstexpr关键字放在模板参数列表后,在返回类型前:
 

写类型无关(Type-Independent)的代码

compare 函数中使用 const T & 作为参数,那么可以传递任何类型的参数进去,扩大了函数的使用范围。而内部都使用 less<T> 可调用对象,less 对象可以处理好指针不是指向同一个数组的情况,若用 < 得到的结果将是未定义的。
模板程序应该尽可能的减少对实参类型的要求。
 
 

模板编译

编译器并不是遇到模板定义时生成代码,而是在实例化特定版本的模板时生成代码。当使用模板时才生成代码将影响到如何组织源代码和错误何时被发现。
通常,调用函数时编译器只要看到函数的声明即可。使用类对象时,编译器需要知道类的定义(class definition)和成员函数的声明,而不需要成员函数的定义,因而,将类定义和函数声明放在头文件中,而函数定义或类成员函数定义则放在源文件中。
不过模板却有不同:为了生成实例,编译器需要知道函数模板的定义以及类模板成员函数(class template member function)的定义。因而,与非模板代码不同,模板代码的头文件中通常包含声明和定义。
函数模板和类模板的成员函数定义通常放在头文件中。
 
模板和头文件
模板包含两种类型的名字:与模板参数无关的、与模板参数有关的。
模板提供者需要保证所有与模板参数无关的名字在模板被使用时是可见的。另外,模板提供者必须保证模板的定义(包括类模板成员定义)在模板被实例化时是可见的;
保证声明所有与实例化模板的类型参数相关的函数、类型和操作符是可见的,如:模板类型参数的成员函数、操作符重载以及内部定义的类型。
模板提供者应该将所有模板定义和定义模板时用到的名字声明都放在头文件中。模板的用户使用时必须包含模板头文件以及用于实例化模板的类型的头文件。
 

编译错误大部分在实例化期间发现

代码直到模板被实例化时才生成影响了何时才能知道模板代码中的编译错误。通常,有三个阶段编译器可能会发现错误。
第一,当编译器模板本身时,可以发现一些语法错误; 第二,当编译器发现模板被使用时,会检查是否参数数目与模板定义一致; 第三,与第二个阶段几乎是在一起发生的,在实例化期间,可以发现类型相关的错误(不存在的操作),取决于编译器如何管理实例,这些错误可能在链接期间被发现。
当书写模板时,不应该让类型过度具体(限制过多),但模板通常都会对其使用的类型做出一些假设。
将由调用者保证传给模板的实参符合模板对类型的要求,即所有的操作都是存在的,并且表现的是符合要求的。
 
  • C++
  • 异常处理类模板
    目录