面向对象编程世界总是以显式接口和运行期多态解决问题。
例如:
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 != someNastyWidget){Widget temp(w);temp.normalize();temp.swap(w);}
}
对于doProcessing内的w,可以这样说:
1.由于w的类型被声明为Widget,所以w必须支持Widget接口。
我们可以在源码中找出这个接口,看看它是什么样子,所以称此为一个显式接口,也就是它在源码中明确可见。
2.由于Widget的某些成员函数是virtual,w对那些函数的调用将表现出运行期多态,也就是说,将于运行期根据w的动态类型决定究竟调用哪一个函数。
在template及泛型编程的世界中,显式接口和运行期多态仍然存在,但重要性降低。反倒是隐式接口和编译期多态移到前面了。
看下面的这段代码:
template<typename T>
void doProcessing(T& w)
{if (w.size() > 10 && w != someNastyWidget){T temp(w);temp.normalize();temp.swap(w);}
}
现在,对于doProcessing内的w,可以这样说:
1.w必须支持哪一种接口,由template中执行于w身上的操作来决定。
在本例中w的类型T好像必须支持size,normalize和swap成员函数等。
2.凡涉及w的任何函数调用,例如oprator >和operator !=,有可能造成template具现化,使这些调用得以成功。这样的具象行为发生在编译期。
“以不同的template参数具现化function template”会导致调用不同的函数,这便是所谓的编译器多态。
“运行期多态”与“编译期多态”的区别类似于“哪一个重载函数该被调用”(发生在编译期)和“哪一个virtual函数该被绑定”(发生在运行期)之间的差异。
现在来看显式接口与隐式接口之间的差异。
通常显式接口由函数的签名式(也就是函数名称、参数类型、返回类型)构成。
例如:
class Widget {
public:Widget();virtual ~Widget();virtual std::size_t size() const;virtual void normalize();void swap(Widget& other);
};
隐式接口就完全不同。它并不基于函数签名式,而是由有效表达式组成。
再次看看doProcessing template一开始的条件:
template<typename T>
void doProcessing(T& w)
{if (w.size() > 10 && w != someNastyWidget){//...}
}
1.它必须提供一个名为size的成员函数,该函数返回一个整数值。
2.它必须支持一个operator !=函数,用来比较两个T对象。这里假设someNastyWidget的类型为T。
隐式接口仅仅由一组有效表达式构成,表达式自身可能看起来很复杂,但它们要求的约束条件一般而言相当直接又明确。
例如以下表达式:
if (w.size() > 10 && w != someNastyWidget)
无论上述表达式导致什么,它都必须与bool兼容。这是template doProcessing加诸于其类型参数T的隐式接口的一部分。
doProcessing要求的其他隐式接口:copy构造函数、normalize和swap也都必须对T型对象有效。
加诸于template身上的隐式接口,就像加诸于class对象身上的显式接口一样真实,而且两者都在编译期完成检查。
就像你无法以一种“与class提供的显式接口矛盾”的方式来使用对象(代码将通不过编译),你也无法在template中使用“不支持template所要求的隐式接口”的对象(代码一样通不过编译)。
总结
1.class和template都支持接口和多态。
2.对class而言接口是显式的,以函数签名为中心。多态则是通过virtual函数发生于运行期。
3.对template参数而言,接口是隐式的,奠基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译期。