模板与泛型编程(Templates and Generic Programming)
模板元编程(Template metaprogramming):在C++编译器内执行并于编译完成时停止执行的程序。
条款41:了解隐式接口和编译器多态(Understand implicit Interface and compile-time polymorphism.)
面向对象编程世界总是以显示接口(explicit interface)和运行期多态(runtime polymorphism)解决问题。
1)Templates及泛型编程的世界,与面向对象有根本上的不同。在此世界中显示接口和运行期仍然存在,但重要性降低。反倒是隐式接口(implicit interface)和编译期多态(compile-time polymorphism)移到前头。
“运行期多态”和“编译期多态”之间的差异,类似于“哪一个重载函数该被调用”(发生在编译期)和“哪一个virtual函数该被绑定”(发生在运行期)之间的差异。
2)显式接口由函数的签名式(也就是函数名称、参数类型、返回类型)构成。隐式接口就完全不同了。它并不基于函数签名式,而是由有效表达式(valid expression)组成。
条款42:了解typename的双重意义(Understand the two meanings of typename.)
1)template内出现的名称如果相依于某个template参数,称之为从属名称(dependent names)。如果从属名称在class内呈嵌套状,称之为嵌套从属名称(nested dependent name)。
一般性规则:任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧临它的前一个位置放上关键字typename。
这个一般性规则的例外是,typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在member initialization list(成员初始列)中作为base class修饰符。
2)声明template参数时,前缀关键字class和typename可互换。
条款43:学习处理模板化基类内的名称(Know how to access names in templatized base classes.)
1)模板全特化(total template specialization):template MsgSender针对类型CompanyZoom特化了,而且其特化是全面性的,也就是说一旦类型参数被定义为CompanyZoom,再没有其他template参数可供变化。
注意class定义式最前头的“template<>”语法象征这既不是template也不是标准class,而是个特化版的MsgSender template,在template实参是CompanyZ时被使用。
2)当编译器遭遇template derived class定义式时,并不知道它继承什么样的class。如果在template derived class内部函数中调用了template base class的函数,编译器不到最后(当template derived class被具现化)并不知道base class的template参数是什么,如果不知道template参数是什么,就无法知道base class看起来像什么,也就是没办法知道它base class是否有那个derived class想调用的函数。
3)令C++“不进入templatized base classes观察”的行为失效的办法:
(1)在base class函数调用动作之前加上“this->”。
(2)使用using声明式。
这里使用using的原因,编译器不进入base class作用域内查找,于是通过using告诉它,请它那么做。
(3)指出被调用的函数位于base class内。
条款44:将与参数无关的代码抽离templates(Factor parameter-independent code out of templates.)
1)Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
2)因非类型模板参数(non-type template parameters)而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。
3)因类型参数(type parameters)而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述(binary representation)的具现类型(instantiation types)共享实现码。
条款45:运用成员函数模板接受所有兼容类型(Use member function templates to accept “all compatible types.”)
1)Templates和泛型编程(Generic Programming)
对任何类型T和任何类型U,可以根据SmartPtr<U>生成一个SmartPtr<T>——因为SmartPtr<T>有个构造函数接受一个SmartPtr<U>参数。这一类构造函数根据对象u创建对象t,而u和t的类型是同一个template的不同具现体,称之为泛化(generalized)copy构造函数。
上述代码使用成员初值列(member initialization list)来初始化SmartPtr<T>之内类型为T*的成员变量,并以类型为U*的指针(由SmartPtr<U>持有)作为初值。这种行为只有当“存在某个隐式转换可将一个U*指针转为一个T*指针”时才能通过编译。
2)请使用member function templates(成员函数模板)生成“可接受所有兼容类型”的函数。
3)如果你声明member templates用于“泛化copy构造”或“泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符。
条款46:需要类型转换时请为模板定义非成员函数(Define non-member functions inside templates when type conversions are desired.)
上述代码编译无法通过的原因是:在template实参推导过程中并不考虑采纳“通过构造函数而发生的”隐式类型转换,因而无法使用Rational<int>的non-explicit构造函数将2转换为Rational<int>,进而将T推导为int。
上述问题的解决方案:template class内的friend声明式可以指涉某个特定函数。
现在对operator*的混合式调用可以通过编译了,因为当对象oneHalf被声明为一个Rational<int>,class Rational<int>于是被具现化出来,而作为过程的一部分,friend函数operator*(接受Rational<int>参数)也就被自动声明出来。后者身为一个函数而非函数模板(function template),因此编译器可在调用它时使用隐式转换函数(例如Rational的non-explicit构造函数)。
上述代码虽然通过编译,却无法连接。最简单的可行办法是将operator*函数本体合并至声明式内:
“令friend函数调用class外部的辅助函数”可以优化代码,因为定义于class内的friend函数也暗自称为inline。
总结:当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。
条款47:请使用traits classes表现类型信息(Use traits classes for information about types.)
1)STL共有5中迭代器分类:
(1)Input迭代器只能向前移动,一次一步,客户只可读取(不能涂写)它们所指的东西,而且只能读取一次。它们模仿指向输入文件的阅读指针(read pointer),C++程序库中的istream_iterators是这一分类的代表。
(2)Output迭代器情况类似,但一切只为输出:它们只向前移动,一次一步,客户只可涂写它们所指的东西,而且只能涂写一次。它们模仿指向输出文件的涂写指针(write pointer),ostream_iterators是这一分类的代表。
(3)forward迭代器,这种迭代器可以做前述两种分类所能做的每一件事,而且可以读或写其所指物一次以上。这使得它们可施行于多次性操作算法(multi-pass algorithms)。
(4)Bidirectional迭代器:它除了可以向前移动,还可以向后移动。STL的list迭代器就属于这一分类,set、multiset、map和multimap的迭代器也都是这一分类。
(5)randomaccess迭代器:它可以执行“迭代器算术”,也就是它可以在常量时间内向前或向后跳跃任意距离。vector、deque和string提供的迭代器都是这一分类。
C++标准程序库提供的专属的卷标结构(tag struct)如下所示:
2)traits是一种技术,也是一个C++程序员共同遵守的协议。这个技术的要求之一是,它对内置(built-in)类型和用户自定义(user-define)类型的表现必须一样好。“traits必须能够施行于内置类型”意味“类型内的嵌套信息(nesting information)”这种东西出局了,因为我们无法将信息嵌套于原始指针内。因此类型的traits信息必须位于类型自身之外。标准技术是把它放进一个template及其一或多个特化版本中。
如何设计并实现一个traits class的方法如下:
STL中有一个名为advance函数,用来将某个迭代器移动某个给定距离。
使用traits技术就可以将advance用于内置类型,:
上述代码中iterator_traits<IterT>::iterator_category在编译器间确定,而if语句却在运行期才会核定。解决这个问题的办法是重载(overloading)。
如何使用一个traits class的总结:
3)Traits classes使得“类型相关信息”在编译期可用。它们以templates和“templates特化”完成实现。
4)整个重载技术(overloading)后,traits classes有可能在编译期对类型执行if...else测试。