EffectiveC++-条款41:了解隐式接口和编译器多态

一. 内容
面向对象的世界总是以显式接口和运行期多态解决问题。举个 Widget 的例子:

class Widget {
public:
    virtual ~Widget()=default;
    virtual void Normalize()=0;
};
class SpecialWidget:public Widget {
public:
    virtual void Normalize() override {
        std::cout<<"Special"<<"\n";
    }
};
class NormalWidget:public Widget {
public:
    virtual void Normalize() override {
        std::cout<<"Normal"<<"\n";
    }
};
inline void BeginPlayWithWidget(Widget& InWidget) {
    InWidget.Normalize();
}
inline void Try() {
    SpecialWidget SpecialWidget;
    NormalWidget NormalWidget;
    BeginPlayWithWidget(SpecialWidget); //Special
    BeginPlayWithWidget(NormalWidget);    //Normal
}


你可以这样说 BeingPlayWithWidget 函数中的 InWidget

由于 InWidget 类型被声明为 Widget,所以 InWidget 必须支持 Widget 接口。我们可以在源码中找到这个接口,看看它们是什么样子,所以我们称之为一个显式接口(explicit interface),也就是它在源码中的确可见。
由于 Widget 的 BeingPlayWithWidget 函数是 virtual ,InWidget 将对此函数的调用表现出运行期多态(runtime polymorphism),也就是说将在运行期根据 InWidget 的动态类型决定究竟调用哪个函数。
这就是显式接口和运行期多态。

Templates及泛型编程的世界,与面向对象的世界有根本的不同。在此世界显式接口和运行期多态仍然存在,但重要性降低。举个例子:

template <typename T>
inline void BeginPlayWithWidgetTemplate(T& InT) {
    if (InT.Size() > 0 && InT != EClassType::None) {
        InT.Normalize();
        std::cout << InT.Size() << "\n";
        //...
    }
}


现在我们怎么描述 InT 呢?

InT 必须支持哪一种接口,是由函数体中对 InT 的操作决定的。从本例来看,InT 的类型 T 必须要支持 Normalize ,Size等操作。看表面可能并非完全正确,但这组操作对于 T 类型的参数来说,是一定要支持的隐式接口(implicit interface)。
凡是涉及 InT 的任何函数调用,例如 operator> 和 operator != 有可能造成 template 的具现化,使得这些调用得以成功,这样的行为发生在编译器。以不同的 template 参数具现化 function templates 会导致调用不同的函数,这便是所谓的编译期多态(compile-time polymorphism)。
这就是隐式接口和编译期多态。

通常显式接口由函数的签名式:函数名称,参数类型,返回类型,常量性构成。隐式接口就不同了,它不基于函数签名式,而是由有效表达式(valid expression)构成。再次看看这个例子:

template <typename T>
inline void BeginPlayWithWidgetTemplate(T& InT) {
    if (InT.Size() > 0 && InT != EClassType::None) {
        //...
    }
}


看样子 T 类型的隐式接口有这些约束:T 必须提供名叫 Size 的成员函数,该函数返回一个整数值;T 必须支持 operator!= 函数,用来与 EClassType 比较。

感谢于操作符重载(operator overloading)带来的可能性,这两个约束都不需要被满足。第一个函数其实只需返回一个类型为 X 的对象,而这个对象可以与 0 一起调用 operator> 函数即可,第二个函数也不一定要求参数类型为 T,任何可被转换为成 operator!= 调用的对象即可。

更复杂一点,我们甚至可以考虑 operator&& 被重载的情况,或许是某些另外行为完全不同的东西。

二. 总结
classes 和 templates 都支持接口(interfaces)和多态(polymorphism)。
对 classes 而言接口是显式的(explicit),以函数签名为中心。多态则是通过 virtual 函数发生于运行期。
对 template 参数而言,接口是隐式的(implicit),奠基于有效表达式。多态则是通过 template 具现化和函数重载解析(function overloading resolution)发生于编译期。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值