目录
- 条款41:了解隐式接口和编译多态
- 条款42:了解typename的双重意义
- 条款43:学习处理模板化基类内的名称
- 条款44:将与参数无关的代码抽离templates
- 条款45:运用成员函数模板接受所有兼容类型
- 条款46:需要类型转换时请为模板定义非成员函数
- 条款47:请使用traits classes表现类型信息
- 条款48:认识模板元编程
条款41:了解隐式接口和编译多态
- 显示接口:在源码中可以找到明确可见的函数接口。
- 隐式接口:不是基于函数签名式,而是有效表达式组成的接口。如下图,函数接口最终实例化成什么函数和模板参数T有关,在代码编译之前是不确定的。
- 运行期多态:在运行期虚函数指针根据指向对象确定哪一个虚函数该被绑定调用。
- 编译期多态:在编译期以不同的模板参数具现化会导致调用不同的函数。
- 对classes而言接口是显式的(explicit),以函数签名为中心。多态则是通过 virtual函数发生于运行期。
- 对template参数而言,接口是隐式的(implicit),奠基于有效表达式。多态则是通过template具现化和函数重载解析(function overloading resolution)发生于编译期。
条款42:了解typename的双重意义
- 当声明template类型参数时,class和typename意义完全相同。建议使用typename。
- template内出现的名称如果相依于某个template参数,称之为从属名称(dependent names)。如果从属名称在 class内呈嵌套状,我们称它为嵌套从属名称(nested dependent name)。
- 当嵌套从属名称表示一个类型时,需要加上typename前缀,表示是一个类型,也叫做嵌套从属类型名称;如果不加typename前缀,默认这个嵌套从属名称是一个非类型(例如某个类的静态成员变量C::value)。
- “typename必须作为嵌套从属类型名称的前缀词”这一规则的例外是,**typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在member initialization list(成员初值列)中作为base class修饰符。**例如:
- 对于冗长的嵌套从属类型名称可用typedef简化:
条款43:学习处理模板化基类内的名称
-
C++拒绝这个调用的原因:它知道base class templates有可能被特化,而那个特化版本可能不提供和一般性 template相同的接口。因此它往往拒绝在 templatized base classes(模板化基类,本例的MsgSender)内寻找继承而来的名称(本例的 sendClear)。实例化前编译器不进入base class作用域内查找。就某种意义而言,当我们从Object Oriented C++跨进Template C++,继承就不像以前那般畅行无阻了。
-
解决方法统一思路:对编译器承诺“base class template的任何特化版本都将支持其一般(泛化)版本所提供的接口”。
-
解决方法1:在base class 函数调用动作之前加上“this->”:
-
解决方法2:使用using声明式提前声明模板基类函数:
-
解决方法3(不推荐):明确指出调用的函数位于模板基类里。若调用的是virtual函数,这种明确修饰会关闭“virtual绑定行为”(只会调用基类的虚函数,而不会调用继承类中对应重写的虚函数):
-
编译器的诊断时间可能发生在早期(当解析derived class template 的定义式时),也可能发生在晚期(当那些templates被特定之template 实参具现化时)。C++的政策是宁愿较早诊断,这就是为什么“当base classes 从templates中被具现化时”
它假设它对那些baseclasses的内容毫无所悉的缘故。
条款44:将与参数无关的代码抽离templates
- 模板代码膨胀:其二进制码带着重复(或几乎重复)的代码、数据,或两者。其结果有可能源码看起来合身而整齐,但目标码(object code) 却不是那么回事。
- Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
- 因非类型模板参数(non- type template parameters) 而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。
- 因类型参数(type parameters)而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述( binary representations)的具现类型( instantiation types)共享实现码。
- 有时愈是尝试精密的做法,事情变得愈是复杂。从某个角度看,一点点代码重复反倒看起来有点幸运了。需要在具体不同平台进行衡量。
条款45:运用成员函数模板接受所有兼容类型
- 以智能指针是“行为像指针”的对象,讨论如何实现成员函数模板用于接受所有兼容类型,泛化智能指针操作类似指针操作。
- 通过成员函数模板建立一个泛化的copy构造函数,使得该拷贝构造函数可以支持智能指针隐式转换。这个行为只有当“存在某个隐式转换可将-一个U指针转为-一个T指针”时才能通过编译,而那正是我们想要的。这个构造函数只在其所获得的实参隶属适当(兼容)类型时才通过编译:
- 通过成员函数模板建立一个泛化的赋值操作“=”函数。下图所有构造函数都是explicit, 惟有“泛化copy构造函数”除外。那意味从某个shared_ ptr 类型隐式转换至另一个shared ptr 类型是被允许的,但从某个内置指针或从其他智能指针类型进行隐式转换则不被认可(如果是显式转换如cast强制转型动作倒是可以)。
- member templates 并不改变语言规则,而语言规则说,如果程序需要一个copy构造函数,你却没有声明它,编译器会为你暗自生成一个。 在class内声明泛化copy构造函数(是个member template)并不会阻止编译器生成它们自己的copy构造函数(一个non-template),所以如果你想要控制copy构造的方方面面,你必须同时声明泛化copy构造函数和“正常的”copy构造函数。相同规则也适用于赋值( assignment)操作。
条款46:需要类型转换时请为模板定义非成员函数
- 模板中,根据实参推导模板参数类型时不支持隐式类型转换推导。如果一个模板类中,模板内的函数需要参数类型转换,成员函数无法做到。函数参数隐式类型转换之前需要确定该函数存在,而实例化之前成员函数并不存在。
- 可在模板类内中采用friend函数声明和定义,使其声明跟随模板类一起实例化,之后再调用的时候就可以自动找到该函数的声明,进行隐式类型转换(下图中可在*重载中输入int类型数据,使其隐式调用Rational的int类型构造函数,隐式转换为Rational<int>)。
- 由于定义于class内中的函数暗自成为inline,可采用class内中friend函数啥也不干,只调用一个外部辅助函数即可,减小inline声明所带来的冲击。
条款47:请使用traits classes表现类型信息
- STL中迭代器可分为五类,继承关系如下图:
(1). Input 迭代器,只能一步一步向前移动,只能读取内容,且仅读取一次。
(2). Output 迭代器,只能一步一步向前移动,只能写入内容,且仅写入一次。
(3). Forward 迭代器,只能一步一步向前移动,可读可写多次。
(4). Bidirectianol 迭代器,只能一步一步向前或向后移动,可读可写多次。
(5). Random Access 迭代器,可以在常量时间以任意步数向前或向后移动,可读可写多次。
-
Traits是一种技术,也是一个C++程序员共同遵守的协议。它对内置类型和用户自定义类型的表现必须一样好。
-
Traits设计实现流程(例子是实现advance模板函数,可以接受各种类型的迭代器进行移动):
-(1)确认若干你希望将来可取得的类型信息;
-(2)为该信息选一个名称;
-(3)提供一个template和一组特例化版本( 例特例化内置指针类型):
-
Traits classes使得“类型相关信息”在编译期可用。它们以templates和“templates特化”完成实现。整合重载技术( overloading) 后,traitsclasses有可能在编译期对类型执行if…else测试。(上述advance中有类型条件判单,是在运行期执行,可采用重载技术使其在编译期进行,仅在advance函数中调用重载函数即可,如下图)。
- 如何使用一个traits class:
(1)建立一组重载函数 (身份像劳工)或函数模板(例如doAdvance) ,彼此间的差异只在于各自的traits参数。令每个函数实现码与其接受之traits信息相应和。.
(2)建立一个控制函数 (身份像工头)或函数模板(例如advance) ,它调用上述那些“劳工函数”并传递traits class所提供的信息。
条款48:认识模板元编程
- Template metaprogramming (TMP,模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。
- TMP可被用来生成“基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。
- 由于template metaprograms执行于C++编译期,因此可将工作从运行期转移到编译期。这导致的一个结果是,某些错误原本通常在运行期才能侦测到,现在可在编译期找出来。另一一个结果是,使用TMP的C++程序可能在每一方面都更高效:较小的可执行文件、较短的运行期、较少的内存需求。然而将工作从运行期移转至编译期的另一个结果是,编译时间变长了。
- TMP中if…else…可通过模板函数重载实现;TMP循环结构可使用递归;TMP递归涉及递归模板具现化,如下图计算阶乘: