0
点赞
收藏
分享

微信扫一扫

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

young_d807 2022-07-27 阅读 60
c++

一. 内容

  1. 面向对象的世界总是以显式接口和运行期多态解决问题。举个 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
    }
    
  2. 你可以这样说 BeingPlayWithWidget 函数中的 InWidget

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

    这就是显式接口运行期多态

  3. 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)。

    这就是隐式接口编译期多态

  4. 通常显式接口由函数的签名式:函数名称,参数类型,返回类型,常量性构成。隐式接口就不同了,它不基于函数签名式,而是由有效表达式(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&& 被重载的情况,或许是某些另外行为完全不同的东西。

二. 总结

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

相关推荐

0 条评论