条款41:了解隐式接口和编译器多态
面向对象编程世界总是以显式接口(函数签名为中心)和运行期多态(通过virtual函数)来解决问题。而templates则是相反,通过隐式接口和编译期多态。隐式接口奠基于有效表达式。编译器多态,在templates的任何函数调用,造成template的具现化,使这些调用得以成功,这样的具现行为发生在编译期。
39. 条款42:了解typename的双重意义
(1)声明templates参数时,前缀关键字class和typename可互换。
(2)在嵌套从属类型名称前应该使用关键字typename标识,但不得在base class list(基类列)或member initialization list(成员初值列)内以它作为base class修饰符。
记住下面这个例子:
template<typename T>
class Derived:public Base<T>::Nested{ //base class list不允许typename
public:
explicit Derived(int x)
:Base<T>::Nested(x) //mem ini list 中不允许typename
{
typename Base<T>::Nested temp;//嵌套从属类型除上述两种情况外,都须加typename
......
}
};
40.条款43:学习处理模板化基类内的名称
令C++不进入“templatized base class观察”的行为失效。有三个办法,第一是在base class函数调用动作之前加上“this->”;第二种方法使用using 声明式;第三种方法是明白指出被调用函数位于base class内,但这种不是一个令人满意的解法,因为如果被调用的是virtual 函数,上述的明确资格修饰会关闭“virtual 绑定行为”。
41.条款44:将与参数无关的代码抽离templates
(1)编写templates时,注意共性和变性的分析。
(2)templates生成多个classes和多个函数,所以任何templates代码都不该与某个造成膨胀的template参数产生相依关系。
(3)因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换templates参数。
(4)因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具体类型共享实现码。
42.条款45:运用成员函数模板接受所有兼容类型
(1)真实指针做得很好的一件事是支持隐式类型转换,derived class可以隐式转换为base class指针,“指向non-const对象”的指针可以转换为“指向const对象”。但智能指针却不行。同一个template的不同具现体之间并没有什么固有的关系(base-derived关系的B、D两类型分别具现化某个template,产生出来的两个具现体并不带有base-derived关系)。
(2)通过member function templates(成员函数模板)生成“可接受所有兼容类型”的函数。
template<typename T>
class SmartPtr{
public:
template<typename U>
SmartPtr(const SmartPtr<U>&other);
//根据U对象创建对象t,而u,v是同一个template的不同具现体.
.....
};
缺点:有可能出现由base对象生成derived对象,没有约束性。
改进:
template<typename T>
class SmartPtr{
public:
template<typename U>
SmartPtr(const SmartPtr<U>&other)
:heldPtr(other.get()){...}
T* get()const{return heldPtr;};
.....
private:
T* heldPtr;
};
//这个行为只有当“存在某个隐式类型转换可将一个U*指针转化为一个T*指针”才能通过编译。
(1) 成员函数模板的效用并不限于构造函数,它们支持赋值操作,如果用于“泛
化copy构造”或“泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符。
如:
template <class T>
class shared_ptr{
public:
shared_ptr(shared_ptr const&r);
template<class Y>
shared_ptr(shared_ptr<Y> const&r);
shared_ptr& operator=(shared_ptr const&r);
template<class Y>
shared_ptr&operator=(shared_ptr<Y>const& r);
......
};
43.条款46:需要类型转换时请为模板定义非成员函数
(1)template实参推导过程中从不将隐式类型转换函数纳入考虑。比如:
Rational<int> oneHalf(1,2);
Rational<int> result = oneHalf * 2;
第二个实参(2)类型是int,编译器无法将2转换为Rational<int>.
(2)当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。
template <typename T> class Rational;
template<typename T>
const Rational<T> doMultipy(const Rational<T>&lhs, const Rational<T>&rhs);
template<typename T>
class Rational{
public:
...
friend const Rational<T> operator*(const Rational<T>&lhs, const Rational<T>&rhs)
{return doMultipy(lhs,rhs);}
...
};
44.条款47:请使用traits classes表现类型信息
(1)STL迭代器五类:
l Input迭代器,只能向前移动,一次一步,客户只可读取它们所指的东西,而且只能读取一次,模仿输入文件的阅读指针。实例:istream_iterators
l Output类似input迭代器,它只能写,客户只可涂写它们所指的东西,而且只能涂写一次,模仿指向输出文件的涂写指针。实例:ostream_iterators
l Forward迭代器,向前移动,读写其所指物一次以上,TR1中hash容器可能为这一类
l Bidirectional迭代器,可以向前,向后移动,常见的list,set,multiset,map,multimap迭代器
l Random access迭代器,可执行迭代器算术,在常量时间内向前或向后跳跃任意距离,这样的算是很类似指针算术,因为它以内置指针为榜样,而内置指针也可被当作random access迭代器使用。实例:vector, deque, string属于这类。
(2)Traits并不是C++关键字或一个预先定义好的构件,它们是一种技术,也是一个C++共同遵守的协议,要求之一是内置类型和用户自定义的类型的表现必须一样好。
(3) traits class使得“类型相关信息”在编译期可用,它们以templates和“templates特化”完成实现
(4)整合重载技术后,traits classes有可能在编译期对类型执行if…..else测试。
(5)如何使用traits classes:
l 建立一组重载函数(身份像劳工)或函数模板,彼此间的差异只在于各自的traits参数,令每个函数的实现码与其接受之traits信息相应和。
l 建立一个控制函数(身份像工头)或函数模板,它调用上述那些“劳工函数”并传递traits class所提供的信息。
45.条款48:认知template元编程
(1)template作用,可将工作从运行期转移到编译期,因而得以实现早期错误侦测和更高的执行效率。
(2)tmp可被用来生成“基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。
记住利用tmp实现阶乘的例子。