面向对象的世界总是以显式接口和运行期多态解决问题。举个 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
可见
。动态类型
决定究竟调用哪个函数。
这就是显式接口
和运行期多态
。
Templates及泛型编程的世界,与面向对象的世界有根本的不同。在此世界显式接口和运行期多态仍然存在,但重要性降低。举个例子:
template <typename T>
inline void BeginPlayWithWidgetTemplate(T& InT) {
if (InT.Size() > 0 && InT != EClassType::None) {
InT.Normalize();
std::cout << InT.Size() << "\n";
//...
}
}
现在我们怎么描述 InT 呢?
这组操作
对于 T 类型的参数来说,是一定要支持的隐式接口(implicit interface)。具现化
,使得这些调用得以成功,这样的行为发生在编译器。以不同的 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&& 被重载的情况,或许是某些另外行为完全不同的东西。