七:模板与泛型编程
条款41:了解隐式接口和编译期多态
面向对象编程总是以显式接口和运行期多态解决问题。Templates及泛型编程与面向对象有根本上的不同,显式接口和运行期多态仍然存在,但重要性降低。反倒是隐式接口和编译期多态更重要。
什么是显式接口,运行期多态,隐式接口,编译期多态?看下面这个例子
class Widget{
public:
Widget();
virtual ~Widget();
virtual std::size_t size() const;
virtual void normalize();
void swap(Widget& other);
};
void doProcessing(Widget& w){
if(w.size() >10 && w!= someNastWidget){
Widget temp(w);
temp.normalize();
temp.swap(w);
}
}
显式接口:在上述的例子中,w的类型被声明为Widget,所以w必须支持Widget接口。我们可以在源码中找出这个接口,看看它是什么样子,所以我称此为一个显式接口,也就是它在源码中明确可见。
运行期多态:由于Widget的某些成员函数是virtual,w对那些函数的调用将表现出运行期多态,也就是说将于运行期根据w的动态类型决定究竟调用哪一个函数。
当我们将doProcessing从函数转变成函数模板:
template <typename T>
void doProcessing(T& w){
if(w.size() >10 && w!= someNastWidget){
Ttemp(w);
temp.normalize();
temp.swap(w);
}
}
隐式接口:w必须支持哪一种接口,系由template中执行于w身上的操作来决定。本例看来w的类型T好像必须支持size,normalize和swap成员函数、copy构造函数。不等比较(!=,不一定需要,当可以类型转换时,T可以转换为别的类型,并不一定需要自己提供)。这一组表达式便是T必须支持的一组隐式接口。
编译期多态:凡涉及w的任何函数调用,例如operator>和operator!=,有可能造成template具现化,使这些调用得以成功。这样的具现行为发生在编译期。“以不同的template参数具现化function template”会导致调用不同的函数,这便是所谓的编译期多态。
请记住:1.classes和template都支持接口和多态。
2.对classes而言接口是显式的,以函数签名为中心。多态则是通过virtual函数发生于运行期。
3.对template参数而言,接口是隐式的,奠基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译期。
条款42:了解typename的双重意义
嵌套从属名称有可能导致解析困难。举个例子:
template<typename C>
void print(const C& container){
C::const_iterator* x;
}
这段代码可能有多重意思:1.声明x为一个local变量,它是一个指针,指向一个C::const_iterator。2.如果C有个static成员变量而碰巧被命名为const_iterator,并且x是个global变量,则是const_iterator与x相乘。
如果解析器在template中遭遇一个嵌套从属名称,他便假设这名称不是个类型。
解决办法:任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧邻它的前一个位置放上关键字typename。如
template<typename C>
void print(const C& container){
typename C::const_iterator* x;
}
请记住:1.声明template参数时,前缀关键字class和typename可互换。
2.请使用关键字typename标识嵌套从属类型名称;但不得在base class lists(基类列)或member initialization list(成员初值列)内以他作为base class修饰符。
条款43:学习处理模板化基类内的名称
template<typename T >
class Derived:public Base<T>{
public:
using Base<T>::sendClear; //成立,告诉编译器,请他假设sendClear位于base class内。
void send(cosnt C% info){
sendClear(info);//这段代码无法通过编译,无法知道继承的Base<T>具体是什么,也就无法知道他是否有个sendClear函数。
this->sendClear(info); //成立,假设sendClear将被继承。
Base<T>::sendClear(info);//成立,但是让人不满意,如果被调用的是virtual函数。明确资格修饰会关闭“virtual绑定行为”
}
};
请记住:可在derived class template内通过“this->”指涉base class template内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成。
条款44:将与参数无关的代码抽离templates
请记住:1.Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
2.因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数。
3.因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码。p217
条款45:运用成员函数模板接受所有兼容类型
本条款转载于 https://blog.csdn.net/lkq_primer/article/details/81135152
真实指针做得很好的一件事是,支持隐式转换,继承类指针可以隐式转换为基类指针,指向non-const对象的指针可以转换为指向const对象的指针。
例如有下面的一个三层继承体系中的一些转换:
class Top{};
class Middle:public Top{};
class Bottom:public Middle{};
Top*pt1 = new Middle;
Top*pt2 = new Bottom;
const Top* pct2 = pt1;
但是如果想在用户自定义的智能指针中模拟上述转换,稍稍有点麻烦,我们希望以下代码通过编译:
template<typename T>
class SmartPtr {
public:
explicit SmartPtr(T* realPtr);
...
};
SmartPtr<Top>pt1 = SmartPtr<Middle>(new Middle);
SmartPtr<Top>pt2 = SmartPtr<Bottom>(new Bottom);
SmartPtr<Top>pct2 = pt1;
但是同一个template的不同具现体之间并不存在什么与生俱来的固有关系,本例中就是SmartPtr 和SmartPtr、SmartPtr之间并不存在什么关系。编译器视它们为完全不同的class。因此我们需要获得这个转换能力,解决方法是使用成员函数模板;如下:
template<typename T>
class SmartPtr {
public:
template<typename U>
SmartPtr(const SmartPtr& other);
...
};
以上代码的意思是,对任何类型T和任何类型U,这里可以根据SmartPtr生成一个SmartPtr。 我们称此构造函数为泛化拷贝构造函数。
我们希望这个泛化拷贝构造函数能够遵守一定的规则,例如我们不希望父类可以向子类转换,我们也不希望根据SmartPtr去创建一个SmartPtr。我们通过对内置指针提供get和初始化列表来达到这个目的。例如下:
template<typename T>
class SmartPtr {
public:
SmartPtr(T*p) :heldPtr(p) {}
template<typename U>
SmartPtr(const SmartPtr<U>& other):heldPtr(other.get()){ }
T* get()const {
return heldPtr;
}
private:
T* heldPtr;
};
这个行为只有当“存在某个隐式转换可将一个U指针转为一个T指针”时才能通过编译,而这正是我们想要的。
请记住
请使用member function template(成员函数模板)生成"可几首所有兼容类型"的函数
如果你声明member template用于"泛化copy构造"或"泛化assignment操作",你还需要声明正常copy构造函数和copy assignment操作符.
条款46:需要类型转换时请为模板定义非成员函数
参考: https://blog.csdn.net/u014038273/article/details/77185919
与条款24相对应。
请记住:当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。
条款47:请使用traits classed表现类型信息
参考:https://www.cnblogs.com/reasno/p/4802737.html
请记住:1.Traits classes使得“类型相关信息”在编译器可用。他们以templates和“templates全特化”完成实现。
2.整合重载技术后,Traits classes有可能在编译期对类型执行 if…else测试。
条款48:认识template元编程
编译器确保所有源码都有效,纵使是不会执行起来的代码。
请记住:1.template Metaprogramming(TMP,模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。
2.TMP可以用来生成“基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。