了解隐式接口和编译期多态——条款41

        面向对象变成世界总是以显式接口(explicit interfaces)和运行期多态(runtime polymorphism)解决问题。举个例子,给定这样(无甚意义)的class:

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);
    }
}

        我们可以这样说doProcessing内的w:

  • 由于w的类型被声明为Widget,所以w必须支持Widget接口。我们可以在源码中找出这个接口(例如在Widget的.h文件中),看看它是什么样子,所以我称此为一个显式接口(explicit interface),也就是它在源码中明确可见。
  • 由于Widget的某些成员函数是virtual,w对那些函数的调用将表现出运行期多态(runtime polymorphism),也就是说将于运行期根据w的动态类型(见条款37)决定究竟调用哪一个函数。

        Templates及泛型编程的世界,与面向对象有根本上的不同。在此世界中显式接口和运行期多态仍然存在,但重要性降低。反倒是隐式接口(implicit interfaces)和编译期多态(compile-time polymorphism)移到前头了。若想知道那是什么,看看当我们将doProcessing从函数转变成函数模板(function template)时发生什么事:

template<typename T>
void doProcessing(T& w)
{
    if (w.size() > 10 && w != someNastyWidget) {
        T temp(w);
        temp.normalize();
        temp.swap(w);
    }
}

        现在我们怎么说doProcessing内的w呢?

  • w必须支持哪一种接口,系由template中执行于w身上的操作来决定。本例看来w的类型T好像必须支持size,normalize和swap成员函数、copy构造函数(用来建立temp)、不等比较。我们很快会看到这并非完全正确,但对目前而言足够真实。重要的是,这一组表达式(对此template而言必须有效编译)便是T必须支持的一组隐式接口(implicit interface)。
  • 凡涉及w的任何函数调用,例如operator>和operator!=,有可能造成template具现化(instantiated),使这些调用得以成功。这样的具现行为发生在编译器。“以不同的template参数具现化function templates”会导致调用不同的函数,这便是所谓的编译器多态。

        纵使你从未用过templates,应该不陌生“运行期多态”和“编译期多态”之间的差异,因为他类似于“哪一个重载函数该被调用”(发生在编译器)和“哪一个virtual函数该被绑定”(发生在运行期)之间的差异。显式接口和隐式接口的差异就比较新颖,需要更多更贴近的说明和解释。

        通常显式接口由函数的签名式(也就是函数名称、参数类型、返回类型)构成。例如Widget class:

class Widget {
public:
    Widget();
    virtual ~Widget();
    virtual std::size_t size() const;
    virtual void normalize();
    void swap(Widget& other);
};

        其public接口由一个构造函数、一个析构函数、函数size,normalize,swap及其参数类型、返回类型、常量性构成。当然也包括编译器产生的copy构造函数和copy assignment操作符(见条款5)。

        隐式接口就完全不同了。它并不基于函数签名式,而是由有效表达式组成。再次看看doProcessing template一开始的条件:

template<typename T>
void doProcessing(T& w)
{
    if (w.size() > 10 && w != someNastyWidget) {
     ...

        T(w的类型)的隐式接口看来好像有这些约束:

  • 它必须提供一个名为size的成员函数,该函数返回一个整数值。
  • 它必须支持一个Opera!=函数,用来比较两个T对象。这里我们假设someNastyWidget的类型为T。

        加诸于template参数身上的隐式接口,就像加诸于class对象身上的显式接口一样真实,而且两者都在编译期完成检查。就像你无法以一种“与class提供之显式接口矛盾”的方式来使用对象(代码将通不过编译),你也无法在template中使用“不支持template所要求之隐式接口”的对象(代码一样通不过编译)。

请记住

  • classes和templates都支持接口和多态
  • 对classes而言接口是显式的(explicit),以函数签名为中心。多态则是通过virtual函数发生于运行期。
  • 对template参数而言,接口是隐式的(implicit),奠基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译期。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值