C++箴言:理解隐式接口和编译期多态
object-oriented programming(面向对象编程)的世界是围绕着 explicit inte易做图ces(显式接口)和 runtime polymorphism(执行期多态)为中心的。例如,给出下面这个(没有什么意义的)的 class(类)。class Widget {
public:
Widget();
virtual ~Widget();
virtual std::size_t size() const;
virtual void normalize();
void swap(Widget& other); // see Item 25
...
};
以及这个(同样没有什么意义)的 function(函数),void doProcessing(Widget& w)
{
if (w.size() > 10 && w != someNastyWidget) {
Widget temp(w);
temp.normalize();
temp.swap(w);
}
}
我们可以这样谈论 doProcessing 中的 w:
·因为 w 被声明为 Widget 类型的引用,w 必须支持 Widget inte易做图ce(接口)。我们可以在源代码中找到这个 inte易做图ce(接口)(例如,Widget 的 .h 文件)以看清楚它是什么样子的,所以我们称其为一个 explicit inte易做图ce(显式接口)——它在源代码中显式可见。
·因为 Widget 的一些 member functions(成员函数)是虚拟的,w 对这些函数的调用就表现为 runtime polymorphism(执行期多态):被调用的特定函数在执行期基于 w 的 dynamic type(动态类型)来确定(参见《 C++箴言:绝不重定义继承的非虚拟函数 》)。
templates(模板)和 generic programming(泛型编程)的世界是根本不同的。在那个世界,explicit inte易做图ces(显式接口)和 runtime polymorphism(执行期多态)继续存在,但是它们不那么重要了。作为替代,把 implicit inte易做图ces(隐式接口)和 compile-time polymorphism(编译期多态)推到了前面。为了了解这是怎样一种情况,看一下当我们把 doProcessing 从一个 function(函数)转为一个 function template(函数模板)时会发生什么:template
void doProcessing(T& w)
{
if (w.size() > 10 && w != someNastyWidget) {
T temp(w);
temp.normalize();
temp.swap(w);
}
}
现在我们可以如何谈论 doProcessing 中的 w 呢?
·w 必须支持的 inte易做图ce(接口)是通过 template(模板)中在 w 身上所执行的操作确定的。在本例中,它显现为 w 的 type (T) 必须支持 size,normalize 和 swap member functions(成员函数);copy construction(拷贝构造函数)(用于创建 temp);以及对不等于的比较(用于和 someNastyWidget 之间的比较)。我们将在以后看到这并不很精确,但是对于现在来说它已经足够正确了。重要的是这一系列必须有效地适合于模板编译的表达式是 T 必须支持的 implicit inte易做图ce(隐式接口)。
·对诸如 operator> 和 operator!= 这样的包含 w 的函数的调用可能伴随 instantiating templates(实例化模板)以使这些调用成功。这样的 instantiation(实例化)发生在编译期间。因为用不同的 template parameters(模板参数)实例化 function templates(函数模板)导致不同的函数被调用,因此以 compile-time polymorphism(编译期多态)著称。
即使你从没有使用过模板,你也应该熟悉 runtime(运行期)和 compile-time polymorphism(编译期多态)之间的区别,因为它类似于确定一系列重载函数中哪一个应该被调用的过程(这个发生在编译期)和 virtual function(虚拟函数)调用的 dynamic binding(动态绑定)(这个发生在运行期)之间的区别。explicit(显式)和 implicit inte易做图ces(隐式接口)之间的区别是与 template(模板)有关的新内容,需要对他进行近距离的考察。
一个 explicit inte易做图ce(显式接口)由 function signatures(函数识别特征)组成,也就是说,函数名,参数类型,返回值,等等。例如,Widget class(类)的 public inte易做图ce(显式接口),class Widget {
public:
Widget();
virtual ~Widget();
virtual std::size_t size() const;
virtual void normalize();
void swap(Widget& other);
};
由一个 constructor(构造函数),一个 destructor(析构函数),以及函数 size,normalize 和 swap 组成,再加上 parameter types(参数类型),return types(返回类型)和这些函数的 constnesses(常量性)。(它也包括 compiler-generated(编译器生成)的 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)——参见《 C++箴言:了解C++偷偷加上和调用了什么 》)它还可能包含 typedefs,还有,如果你胆大包天敢于违背让 data members(数据成员)private(私有)的建议,即使在这种情况下,这些 data members(数据成员)也不是。
一个 implicit inte易做图ce(隐式接口)有很大不同。它不是基于 function signatures(函数识别特征)的。它是由 valid expressions(合法表达式)组成的。再看一下在 doProcessing template 开始处的条件:template
void doProcessing(T& w)
{
if (w.size() > 10 && w != someNastyWidget) {
...
对于 T(w 的类型)的 implicit inte易做图ce(隐式接口)看起来有如下这些约束:
·它必须提供一个名为 size 的返回一个正数值的 member function(成员函数)。
·它必须支持一个用于比较两个类型 T 的对象的 operator!= 函数。(这里,我们假定 someNastyWidget 的类型为 T。)
由于 operator overloading(运算符重载)的可能性,这两个约束都不必满足。是的,T 必须支持一个 size member function(成员函数),值得提及的是虽然这个函数可以是从一个 base class(基类)继承来的。但是这个 member function(成员函数)不需要返回一个整数类型。它甚至不需要返回一个数值类型。对于这种情况,它甚至不需要返回一个定义了 operator> 的类型!它要做的全部就是返回类型 X 的一个 object(对象),有一个 operator> 可以用一个类型为 X 的 object(对象)和一个 int(因为 10 为 int 类型)来调用。这个 operator> 不需要取得一个类型 X 的参数,因为它可以取得一个类型 Y 的参数,只要在类型 X 的 objects(对象)和类型 Y 的 objects(对象)之间有一个 implicit conversion(隐式转型)就可以了!
类似地,T 支持 operator!= 也是没有必要的,因为如果 operator!= 取得一个类型 X 的 objects(对象)和一个类型 Y 的 objects(对象)是可接受的一样。只要 T 能转型为 X,而 someNastyWidget 的类型能够转型为 Y,对 operator!= 的调用就是合法的。
(一个旁注:此处的分析没有考虑 operator&& 被重载的可能性,这会将上面的表达式的含义从与转换到某些大概完全不同的东西。)
第一次考虑 implicit inte易做图ces(隐式接口)的时候,大多数人都会头疼,但是他们真的不需要阿司匹林。implicit inte易做图ces(隐式接口)简单地由一套 valid expressions(合法表达式)构成。这些表达式自身看起来可能很复杂,但是它们施加的约束通常是简单易懂的。例如,给出这个条件,if (w.size() > 10 && w != someNastyWidget) ...
关于 functions size,operator> ,operator&& 或 operator!= 上的约束很难说出更多的东西,但是要识别出整个表达式的约束是非常简单的。一个 if 语句的条件部分必须是一个 boolean expression(布尔表达式),所以不管 "w.size() > 10 && w != someNastyWidget" 所产生的类型涉及到的精确类型,它必须与 bool 兼容。这就是 template(模板)doProcessing 施加于它的 type parameter(类型参数)T 之上的 implicit inte易做图ce(隐式接口)的一部分。被 doProcessing 需要的 inte易做图ce(接口)的其余部分是 copy constructor(拷贝构造函数),normalize 和 swap 的调用对于类型 T 的 objects(对象)来说必须是合法的。
implicit inte易做图ce(隐式接口)对 template(模板)的 parameters(参数)施加的影响正像 explicit inte易做图ces(显式接口)对一个 class(类)的 objects(对象)施加的影响,而且这两者都在编译期间被检查。正像你不能用与它的 class(类)提供的 explicit inte易做图ce(显式接口)矛盾的方法使用 object(对象)(代码无法编译)一样,除非一个 object(对象)支持 template(模板)所需要的 implicit inte易做图ce(隐式接口),否则你就不能在一个 template(模板)中试图使用这个 object(对象)(代码还是无法编译)。
Things to Remember
·classes(类)和 templates(模板)都支持 inte易做图ces(接口)和 polymorphism(多态)。
·对于 classes(类),inte易做图ces(接口)是 explicit(显式)的并以 function signatures(函数识别特征)为中心的。polymorphism(多态性)通过 virtual functions(虚拟函数)出现在运行期。
·对于 template parameters(模板参数),inte易做图ces(接口)是 implicit(隐式)的并基于 valid expressions(合法表达式)。polymorphism(多态性)通过 template instantiation(模板实例化)和 function overloading resolution(函数重载解析)出现在编译期。