C++ template最初的发展动机:建立“类型安全type safe”的容器如vector,lis,map.后面随着使用的增多,我们发现模版有能力完成更多可能的变化。
容器虽好,但泛型编程(generic programming)更好:代码和其所处理的对象类型彼此独立。STL算法如for_each,find等就是这类编程的成果。
于是导出了模板元编程(tmplate metaprogramming):”C+编译器内执行,并于编译完成时停止执行”
面向对象编程总是以显式接口(explicit interfaces)和运行期多态(runtime polymorphism)解决问题.
class Widget
{
public:
Widget();
virtual ~Widget();
virtual std::size_t size()const;
virtual void normalize();
void swap(Widget&other); //TK25
}
void doProcess(Widget& w) //无意义的函数
{
if(w.size()>10&&w!=someNastyWidget)
{
Widget tmp(w);
tmp.normalize();
tmp.swap(w);
}
} //我们可以说doProcess内的w
从上面代码中我们知道:
1、w的类型被声明为Widget,w必须支持Widget接口。我们可以在源码中找到这个接口(Widget的.h文件)。我们称此为一个显示接口、即它在源码中明确可见。
2、Widget的成员函数有virtual的,w对那些函数表现出运行时多态。即根据动态类型决定调用哪一个函数。
模版和范型编程与面向对象有根本上的不同,虽然显示接口和运行多态依然存在,但重要性变低。而隐式接口和编译期多态相对更重要。考虑以下代码:
template<typename T>
void doProcess(T& w) {
if(w.size()>10&&w!=someNastyWidget)
{
T tmp(w);
tmp.normalize();
tmp.swap(w);
}
}
从上述代码我们可以知道:
1、w支持哪种接口,由模版执行于w上的操作决定。如本例中w必须支持size,normalize,swap,copy构造、不等比较等(虽然不完全正确)。这组表态式(对该模板而言必须有效编译)是T必须支持的一组隐式接口。
2、涉及w的调用,可能实例化模板。“以不同的模板参数实例化函数模版”会导致不同的函数调用。这就是所谓的编译期多态。
类似于“哪一个重载函数被调用”(发生在编译期)和“哪一个虚函数被绑定”(运行期)。
通常显式接口由函数签名式(函数名,参数类型、返回类型)构成.如
class Widget
{
public:
Widget();
virtual ~Widget();
virtualstd::size_t size() const;
virtualvoid normalize();
void swap(Widget&other); //TK25
}
如上,public接口由一个构造、一个析构、三个函数及其参数类型、返回值、常量性构成。当然也包括编译产生的复制构想和copy assignmeng操作符。当然也可以也包括typedef等。
隐式接口完全不同,并不基于签名式,而是由有效表达式组成。再次看下面函数模版的初始条件:
template<typename T>
void doProcess(T& w) {
if(w.size()>10&&w!=someNastyWidget)
……
}
T的隐式接口似乎有这些约束
1、它必须提供一个size函数,该函数返回一个整数值。
2、必须支持一个operator!= 函数,用于比较两个T对象。这里我们假设someNastyWidget类型为T
这要感谢操作符重载带来的可能性,这两个约束都不需要满足。虽然T必须要支持size函数,但这个函数可以从基类继承而来。同时这个成员函数甚至不要返回一个整数值或整数类型,更甚至不要定义operator>操作类型。唯一要做的是返回一个X对象,这个对象加个一个int(10)的类型就能调用一个operator>。同时operator不需要非取得一个类型为X的参数不可,也可以取得类型为Y的参数,只需要一个隐式转换可以将X转换成Y对象。
同理类推:T并不需要支持operator=,因为这样也是可以的:operator!= 接受一个类型为X和类型为Y的对象,T可以转换为X而someNastyWidget可以转换为Y。这样就可以直接调用operator!=
上面表达方式或许很复杂,但实际而言却不是,因为它们的约束条件相当直接又明确。如:
if(w.size()>10&&w!=someNastyWidget)
如其约束条件,虽然多,但整体却很容易。if条件式必须是个bool表达式。无论涉及什么也无论表达式导致什么,只需与bool兼容就行了。这是模版函数doProcess加诸于其他类型参数T的隐式接口的一部分。该函数要求的其他隐式接口:如copy构造、normalize、swap也必须对T型对象有效。
模版参数身上的隐式接口,和类对象身上的显式接口一样。都在编译期间完成检查。“与类提供显示接口矛盾”的方式使用对象 和 在模版中使用“不支持模版要求的隐式接口”对象都不能通过编译。
需要记住的:
1、类和模版都支持接口和多态。
2、类接口是显示的,以函数声明为中心。多态通过虚函数发生于运行期。
3、对模版参数而言,接口是隐式的,基于有效表达式。多态通过模版实例化和函数重载解析,发生于编译期。