一. 内容
面向对象的世界总是以显式接口和运行期多态解决问题。举个 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)发生于编译期。