面向对象编程世界总是以显式接口和运行期动态解决问题。如下代码所示:
class Widget { public: Widget(); virtual ~Widget(); virtual std::size_t size() const; virtual void normalize(); void swap(Widget& other); // 条款25 ..... }; void doProcessing(Widget& w) { if (w.size() > 10 && w != someNastyWidget) { Widget temp(w); temp.normalize(); temp.swap(w); } }
1.由于w的类型被声明为Widget,所以w必须支持Widget接口。我们可以在源码中找出这个接口,看看它是什么样子,所以我们称为一个显式接口,也就是它在源码中明确可见。
2.由于Widget的某些成员函数是virtual,w对那些函数的调用将表现出运行期多态,也就是说将于运行期根据w的动态类型(条款37)决定究竟调用哪一个函数。
Templates及泛型编程的世界,与面向对象有根本不同。在此世界中显式接口和运行期多态仍然存在,但重要性降低。反倒是隐式接口和编译器多态更显重要了。如下:
template<typename T> void doProcessing(T& w) { if (w.size() > 10 && w != someNastyWidget) { Widget temp(w); temp.normalize(); temp.swap(w); } }
3.w 必须支持哪一种接口,系由template中执行于w身上的操作来决定。本例看来w的类型T好像必须支持size,normalize和swap成员函数,copying函数(用来建立temp),不等比较(用来比较someNasty-Widget)等等。这一组表达式(对此template而言必须有效编译)便是T必须支持的一组隐式接口。
4.凡涉及w的任何函数调用,例如operator>和operator!=,有可能造成template具现化,使这些调用得以成功。这样的具现行为发生在编译期。“以不同的template参数具现化function templates(函数模板)”会导致调用不同的函数,这个便是所谓的编译期多态。(“哪一个函数应该被调用”——发生在编译期; “哪一个virtual函数该被绑定”——发生在运行期)。
通常,显式接口由函数签名式(也就是函数名称、参数类型、返回类型)构成。
隐式接口就完全不同了。它并不基于函数签名式,而是由有效表达式组成。
template参数身上的隐式接口,就像class对象身上的显示接口一个样真实,二者都在编译期完成检查。无法在template中使用“不支持template所要求之隐式接口”的对象,代码编译通不过。
请记住:
1. classes 和 templates 都支持接口和多态。
2. 对classes而言接口是显式的,以函数签名为中心。多态则是通过virtual函数发生于运行期。
3. 对template参数而言,接口是隐式的,奠基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译期。