OOC:Class Hierarchy Maintainability
来自 ChinaUnix Wiki
Class Hierarchy Maintainability 类树的可维护性
Requirements 需求
Inheritance lets us evolve general data types into more specialized ones and spares us recoding basic functionality. Dynamic Linkage helps us repair the shortcomings that a more general data type might have. What we still need is a clean global organization to simplify maintaining a larger system of classes:
继承使一般数据类型可以演变成更专用的数据类型,我们因此不必重写基本的功能;动态连接帮我们修复一个一般数据类型可能有的缺陷。我们需要的是一个清晰的全面的机制,简化一个更大的类系统的维护:
(1) all dynamic links have to point to the correct methods — e.g., a constructor should not be inserted in the wrong place in a class description;
(2) we need a coherent way to add, remove, or change the order of dynamically linked methods for a superclass while guaranteeing correct inheritance to its subclasses;
(3) there should be no loopholes such as missing dynamic links or undefined methods;
(4) if we inherit a dynamically linked method, the implementation of the superclass from which we inherit must remain absolutely unchanged, i.e., inheritance must be possible using binary information only;
(5) different sets of classes should be able to have different sets of dynamically linked methods — e.g., only Point and Circle from chapter 4, but not the sets from chapter 1 or the expression nodes from chapter 3 and 5, have a use for a draw() method.
(1) 所有动态连接必须指向正确的方法——比如,构造函数不应该置入类描述中不正确的地方;
(2) 我们需要一个明晰一致的方式增减或改变基类方法的排序,同时保证它的子类正确的继承;
(3) 没有丢失的动态连接或是未定义的方法这样的隐患;
(4) 如果继承动态连接方法,被继承基类的实现必须绝对保持不变,即,继承一定要可以只用二进制信息;
(5) 类的不同组合应该能有不同动态连接方法的组合,例如,只有第4章的Point和Circle——而不是第1章或第3章、第5章中表示节点的组合——才可以使用draw()方法。
Mostly, this list indicates that maintaining dynamic linkage is difficult and error-prone — if we cannot substantially improve the situation we may well have created a white elephant.
这个清单表明维护动态连接是困难而且容易出错的。如果我们不能从根本上改变这个局面,我们可能造出了一头华而不实的白象(白象在南亚民族文化里与佛祖出生相关,法律禁止让白象劳作,因此白象一词,用来形容代价昂贵却不能使用的东西——译著)
So far we have worked with a single list of dynamically linked methods, regardless of whether or not it made sense for a particular class. The list was defined as struct Class and it was included wherever dynamic linkage needed to be initialized. Thanks to function prototypes, ANSI-C will check that function names like Point_ctor fit the slots in the class description, where they are used as static initial¬izers. (1) above is only a problem if several methods have type compatible inter¬faces or if we change struct Class and do a sloppy recompilation.
至此我们使用的是动态连接方法的单一链表,而不管它是否于一个具体的类有意义。这个链表的定义是struct Class,而且只要动态连接需要初始化,它就包括在内。感谢函数原型,ANSI-C会检查像Point_ctor这样的函数名字,看看它们是否适合放入类描述中,在那里它们是作为静态启动函数使用的。上述(1)仅当几个方法的接口类型相容或者我们改变了struct Class要拖泥带水重新编译的情况下才是一个问题。
Item (2), changing struct Class, sounds like a nightmare — we need to manu¬ally access every class implementation to update the static initialization of the class description, and we can easily forget to add a new method in some class, thus causing problem (3).
第(2)项,改变struct Class,似乎会是场灾难:我们要逐个访问每个类的实现来更新这个类描述的静态初始化,而且我们会很容易地忘掉在某些类中增加新的方法,导致(3)中的问题发生。
We had an elegant way to add assignment to the calculator in section 5.6: we changed the source code and made the dynamically linked methods for binary nodes from section 3.6 public so that we could reuse them as initializers for the Assign description, but this clearly violates requirement (4).
在5.6节里,我们有一个精巧的方法给计算器增加赋值功能:我们修改源程序,使3.6节二元运算节点的动态连接方法的属性变为公开,这样我们是能在Assign描述中再用这些方法作初始化函数。但是,这样做显然违背了需求(4)。
If maintaining a single struct Class sounds like a challenge already, (5) above suggests that we should have different versions of struct Class for different sets of classes! The requirement is perfectly reasonable, however: every class needs a constructor and a destructor; for points, circles, and other graphical objects we add drawing facilities; atoms and strings need comparisons; collections like sets, bags, or lists have methods to add, find, and remove objects; and so on.
如果维护单个struct Class就已经是个挑战,那么第上述(5)条就意味着对不同的类组合要使用不同版本的struct Class。这个要求完全合理,不过这样一来每个类都要一个构造函数和析构函数,点、圆及其它图形对象要增加绘图工具,字串元和字符串要增加比较操作,集合、包和链表这样的复合结构要有方法来增减和寻找对象等等。
Metaclasses 元类
It turns out that requirement (5) does not compound our problems — it actually points the way to solving them. Just like a circle adds information to a point, so do the class descriptions for points and circles together add information — a polymorphic draw() — to the class description for both of these two classes.
看起来第(5)项需求没有使问题复杂化,相反,它给我们指明了解决问题的方向。如同圆是点的延伸一样,相对于这两个类的共通的类描述而言,点的类描述和圆的类描述的综合增加了信息:多态的draw()方法。
Put differently: As long as two classes have the same dynamically linked methods, albeit with different implementations, they can use the same struct Class to store the links — this is the case for Point and Circle. Once we add another dynamically linked method, we need to lengthen struct Class to provide room for the new link — this is how we get from a class with only a constructor and a destructor to a class like Point with a .draw component thrown in.
换句话说,只要两个类有同样的动态连接方法——即使有不同的实现——那它们就能用同样的struct Class保存连接。Point和Circle两个类就是如此。一旦我们加入另一个动态连接方法,我们就要扩展struct Class容纳新的连接。从一个只有构造和析构函数的类,到像点Point这样有draw方法的类,我们所做的就正是这样的扩展。
Lengthening structures is what we called inheritance, i.e., we discover that class descriptions with the same set of methods form a class, and that there is inheritance among the classes of class descriptions!
扩展结构就是我们所说的继承,就是说,我们发现同样一组方法的类描述形成一个类,并且类描述构成的类之间有继承关系。
We call a class of class descriptions a metaclass. A metaclass behaves just like a class: Point and Circle, the descriptions for all points and all circles, are two objects in a metaclass PointClass, because they can both describe how to draw. A metaclass has methods: we can ask an object like Point or Circle for the size of the objects, points or circles, that it describes, or we could ask the object Circle if Point, indeed, describes the superclass of the circles.
我们将类描述构成的类称作元类。一个元类,其行为恰如一个普通类:点Point和圆Circle,作为所有点和所有圆的描述,是元类PointClass的两个对象,因为两者都能描述如何绘(划)。一个元类有方法。我们可以查问一个对象(如点Point或圆Circle)所描述的对象(点或圆)的大小,我们甚至可以询问对象圆Circle是否点Point确实描述了圆的基类。
Dynamically linked methods can do different things for objects from different classes. Does a metaclass need dynamically linked methods? The destructor in PointClass would be called as a consequence of delete(Point) or delete(Circle), i.e., when we try to eliminate the class description for points or circles. This des¬tructor ought to return a null pointer because it is clearly not a good idea to elim¬inate a class description. A metaclass constructor is much more useful:
动态连接方法功能可以因类而异。元类是否需要动态连接方法?在元类PointClass里,当要终止点或圆的类描述时,其析构函数可能得是顺序调用delete(Point)或delete(Circle)。显然,清除一个类描述并不好,所以这个析构函数应当返回空指针。元类的构造函数却有用得多:
Circle =new(PointClass,/*ask the metaclass */
"Circle", /*to make a class description */
Point, /* with this superclass, */
sizeof(struct Circle), /* this size for the objects, */
ctor, Circle_ctor, /* this constructor, */
draw, Circle_draw, /* and this drawing method. */
0); /* end of list */
This call should produce a class description for a class whose objects can be con¬structed, destroyed, and drawn. Because drawing is the new idea common to all class descriptions in PointClass, it seems only reasonable to expect that the PointClass constructor would at least know how to deposit a link to a drawing method in the new description.
调用这个构造函数应该生成一个类描述,其类的对象可以生成、销毁和绘图。因为绘图是PointClass中所有类描述都适用的新创意,期望PointClass的构造函数应当至少知道如何存放一个连接,指向在新的描述中的绘图方法,这是再也合理不过了。
Even more is possible: if we pass the superclass description Point to the PointClass constructor, it should be able to first copy all the inherited links from Point to Circle and then overwrite those which are redefined for Circle. This, how¬ever, completely solves the problem of binary inheritance: when we create Circle we only specify the new methods specific to circles; methods for points are impli¬citly inherited because their addresses can be copied by the PointClass construc¬tor.
不仅如此,如果我们把基类描述Point传给PointClass构造函数,那么应该能首先从Point到Circle复制所有继承的连接然后重写那些在Circle中重新定义的方法。而这样一来,我们完全解决了二元继承的问题:当我们创建Circle时,我们只要指定圆类所特有的新方法,点类的方法都隐式地继承下来,因为它们的地址可以被PointClass的构造函数复制。
Roots — Object and Class 根:Object和Class
Class descriptions with the same set of methods are the objects of a metaclass. A metaclass as such is a class and, therefore, has a class description. We must assume that the class descriptions for metaclasses once again are objects of meta (metameta?) classes, which in turn are classes and ...
同一组方法的类描述就是元类的对象。如此一个元类也是一个类,因此有自己的类描述。再说一次,我们一定要这样想象,元类的类描述是元类(关于关于类的描述的描述?)的对象,而这对象本身又是类和…
It seems unwise to continue this train of thought. Instead, let us start with the most trivial objects imaginable. We define a class Object with the ability to create, destroy, compare, and display objects.
Interface Object.h:
看起来顺着这样的思路下去是不明智的。相反,让我们从可以想象出的最简约的对象着手。我们定义一个对象类Object,它能创建、销毁,比较和显示对象。
接口头文件Object.h:
extern const void * Object; /* new(Object); */ void * new (const void * class, ...); void delete (void * self); int differ (const void * self, const void * b); int puto (const void * self, FILE * fp);
Representation Object.r:
结构头文件Object.r:
struct Object {
const struct Class * class; /* object’s description */
};
Next we define the representation for the class description for objects, i.e., the structure to which the component .class in struct Object for our trivial objects points. Both structures are needed in the same places, so we add to Object.h:
下一步我们定义对象的类描述的表示,即我们简约对象中struct Object里.class分量所指向的结构。因为两个结构都在同一地方要用,我们将其加入Object.h:
extern const void * Class; /* new(Class, "name", super, size, sel, meth, ... 0); */
and to Object.r:
和Object.r:
struct Class {
const struct Object _; /* class’ description */
const char * name; /* class’ name */
const struct Class * super; /* class’ super class */
size_t size; /* class’ object’s size */
void * (* ctor) (void * self, va_list * app);
void * (* dtor) (void * self);
int (* differ) (const void * self, const void * b);
int (* puto) (const void * self, FILE * fp);
struct Class is the representation of each element of the first metaclass Class. This metaclass is a class; therefore, its elements point to a class description. Point¬ing to a class description is exactly what an Object can do, i.e., struct Class extends struct Object, i.e., Class is a subclass of Object!
struct Class是第一个元类里每一个元素的表示。这个元类是一个类,因此它的元素指向类描述。指向一个类描述正是一个Object能做的,即struct Class扩展struct Object,也即Class是Object的子类。
This does not cause grief: objects, i.e., instances of the class Object, can be created, destroyed, compared, and displayed. We have decided that we want to create class descriptions, and we can write a destructor that silently prevents that a class description is destroyed. It may be quite useful to be able to compare and display class descriptions. However, this means that the metaclass Class has the same set of methods, and therefore the same type of description, as the class Object, i.e., the chain from objects to their class description and from there to the description of the class description ends right there. Properly initialized, we end up with the following picture:
这不会导致灾难:对象,即类Object的实例,可以被创建、终结、比较和显示。我们决定了要创建类描述,我们能写出析构函数,它自己防止类描述被终结。能够比较和显示类描述可能很有用处。不过,这就意味着元类Class与类Object有同样一组方法因而同样的描述。也就是说,从对象到它们的类描述再由此到类描述的描述这样一个链路就此止住。经过恰当的初始化,我们得到这样一幅图象:
The question mark indicates one rather arbitrary decision: does Object have a superclass or not? It makes no real difference, but for the sake of uniformity we define Object to be its own superclass, i.e., the question mark in the picture is replaced by a pointer to Object itself.
问号表明一个任意的选择。Object有没有基类?有没有实际上没有差别。不过,为一致起见,我们定义Object为其自身的基类,即图中的问号用指向Object自身的箭头代替。
Subclassing — Any 子类Any
Given the descriptions Class and Object, we can already make new objects and even a new subclass. As an example, consider a subclass Any which claims that all its objects are equal to any other object, i.e., Any overwrites differ() to always return zero. Here is the implementation of Any, and a quick test, all in one file any.c:
给定Class和Object的描述,我们已经可以创建新的对象甚至新的子类。例如,考虑一个子类Any,它声明其所有对象都是相同的,就是说,Any重写了differ()方法使其总是返回0。下面就是Any的实现和一个简捷的测试,全部放在一个文件any.c中:
#include "Object.h"
static int Any_differ (const void * _self, const void * b)
{
return 0; /* Any equals anything... */
}
int main ()
{ void * o = new(Object);
const void * Any =
new(Class, "Any", Object, sizeOf(o),
differ, Any_differ,
0);
void * a = new(Any);
puto(Any, stdout);
puto(o, stdout);
puto(a, stdout);
if (differ(o, o) == differ(a, a))
puts("ok");
if (differ(o, a) != differ(a, o))
puts("not commutative");
delete(o), delete(a);
delete(Any);
return 0;
}
If we implement a new class we need to include the interface of its superclass. Any has the same representation as Object and the class is so simple that we do not even need to include the superclass representation file. The class description Any is created by requesting a new instance from its metaclass Class and con¬structing it with the new class name, the superclass description, and the size of an object of the new class:
如果我们实现一个新的类,我们要包括它所有基类的接口。Any有与Object同样的表达,而且其类如此简单,我们甚至不必包括基类的结构头文件。类描述Any是通过申请描述类Class新的实例并以新的类名、基类描述、新类对象的大小来构造而成的:
const void * Any = new(Class, "Any", Object, sizeOf(o), differ, Any_differ, 0);
Additionally, we specify exactly those dynamically linked methods, which we overwrite for the new class. The method names can appear in any order, each is preceded by its selector name. A zero terminates the list.
还有,我们只列出那些我们在新类中要重写的动态连接方法,方法的名字可以以任何顺序排列,只要它跟着相应的选择器名字,0则标志这份清单的结束。
The program generates one instance o of Object and one instance a of Any, and displays the new class description and the two instances. Either instance can¬not differ from itself, so the program prints ok. The method differ() has been overwritten for Any; therefore, we get different results if we compare o to a, and vice versa:
这个程序生成Object的实例o和Any的实例a,显示新类描述和这两个实例。无论哪个实例都不可能与自身不同,所有程序输出ok。Any的方法differ()已经重写,所以如果拿o比a以及反过来比较,我们得到不同的输出
$ any
Class at 0x101fc
Object at 0x101f4
Any at 0x10220
ok
not commutative
Any: cannot destroy class
Clearly, we should not be able to delete a class description. This error is already detected during compilation, because delete() does not accept a pointer to an area protected with const.
明确地说,我们不应该能够删除类描述。这一错误在编译中已经发现,因为delete()不接受指向保护区const的指针。
Implementation — Object 实现:Object
Implementing the Object class is straightforward: the constructor and destructor return self, and differ() checks if its two argument pointers are equal. Defining these trivial implementations is very important, however: we use a single tree of classes and make Object the ultimate superclass of every other class; if a class does not overwrite a method such as differ() it inherits it from Object, i.e., every class has at least a rudimentary definition for every dynamically linked method already applicable to Object.
实现Object类是直截了当的:构造函数和解析函数返回self,differ()检查它的两个变元指针是否相等。然而,定义这些琐碎的实现却是非常重要的。我们使用单一的类树并且使得Object是所有其它类的终极基类。如果一个类不重写象differ()这样的方法,那么它就从Object继承这一方法,即每个类对已经适用Object的每个动态连接方法至少有一个基本的定义。
This is a general safety principle: whenever we introduce a new dynamically linked method, we will immediately implement it for its first class. In this fashion we can never be caught selecting a totally undefined method. A case in point is the puto() method for Object:
这是个一般性的安全法则:任何时候引入动态连接方法,我们第一时间在它的第一个类实现它。如此行事,我们绝不会犯使用未定义方法的错误。一个恰当的例子是Object的puto()方法:
static int Object_puto (const void * _self, FILE * fp)
{ const struct Class * class = classOf(_self);
return fprintf(fp, "%s at %p\n", class —> name, _self);
}
Every object points to a class description and we have stored the class name with the description. Therefore, for any object we can at least display the class name and the address of the object. The first three lines of output from the trivial test program in section 6.4 indicate that we have not bothered to overwrite this method for Class or Any.
每个对象指向一个类描述,而且我们已经保存了这个类名与其描述。因此,任何对象我们都可以至少显示类名及对象地址。6.4节的测试程序输出的头三行说明我们没有重写Class或Any的这一方法。
puto() relies on an access function classOf() which does some safety checks and returns the class descriptor for an object:
puto()有赖于一个访问函数classOf(),后者做些安全检查并返还对象的类描述符:
const void * classOf (const void * _self)
{ const struct Object * self = _self;
assert(self && self —> class);
return self —> class;
}
Similarly, we can ask an object for its size* — remember that, technically, an object is a plain void * in ANSI-C:
类似地,可以询问对象的大小(别忘了,在ANSI C中,对象不过是无类型的指针void *):
size_t sizeOf (const void * _self)
{ const struct Class * class = classOf(_self);
return class —> size;
}
作者原注: 此处的sizeOf与sizeof非常容易混淆。我恰恰喜欢这样的一语双关:误用sizeof将只能得到指向对象的指针的大小而非对象的大小。 发明好的方法名字的确是一种艺术。
It is debatable if we should ask the object for the size, or if we should only ask it for the class and then explicitly ask the class for the size. If we implement sizeOf() for objects, we cannot apply it to a class description to get the corresponding object size — we will get the size of the class description itself. However, practical use indicates that defining sizeOf() for objects is preferable. In contrast, super() is a statically linked method which returns the superclass of a class, not of an object.
是应该直接询问对象的大小,还是应该先问其所属类再问类的大小,尚无定论。如果我们针对对象实现sizeOf(),那我们无法将其用于类描述得到它描述的对象大小——我们得到的将是类描述本身的大小。然而,实用中显示对对象定义sizeOf()更可取。相反,supper()是静态连接方法,返回一个类而非对象的基类。
Implementation — Class 实现:Class
Class is a subclass of Object, so we can simply inherit the methods for comparison and display. The destructor returns a null pointer to keep delete() from actually reclaiming the space occupied by a class description:
Class是Object的子类,所以我们可以简单地继承比较和显示的方法。析构函数返回空指针,使得delete()不必真正释放类描述所占用的空间:
static void * Class_dtor (void * _self)
{ struct Class * self = _self;
fprintf(stderr, "%s: cannot destroy class\n", self—>name);
return 0;
}
Here is the access function to get the superclass from a class description:
下面就是这个从类描述得到基类的访问函数:
const void * super (const void * _self)
{ const struct Class * self = _self;
assert(self && self —> super);
return self —> super;
}
The only difficult part is the implementation of the Class constructor because this is where a new class description is initialized, where inheritance takes place, and where our four basic methods can be overwritten. We recall from section 6.4 how a new class description is created:
唯一困难的部分是Class构造函数的实现,因为它要初始化新的类描述,处理继承,并重写四个基本的方法。回顾一下6.4节里如何创建新的类描述:
const void * Any = new(Class, "Any", Object, sizeOf(o), differ, Any_differ, 0);
This means that our Class constructor receives the name, superclass, and object size for a new class description. We start by transferring these from the argument list:
这说明Class构造函数接受名字、基类和对象大小作为新的类描述。我们从变元列表传送这些描述项开始:
static void * Class_ctor (void * _self, va_list * app)
{ struct Class * self = _self;
self —> name = va_arg(* app, char *);
self —> super = va_arg(* app, struct Class *);
self —> size = va_arg(* app, size_t);
assert(self —> super);
self cannot be a null pointer because we would not have otherwise found this method. super, however, could be zero and that would be a very bad idea.
self不可以是空指针,要不然我们会找不到这个方法。不过,super却可以为空——虽然这样做很不好。
The next step is inheritance. We must copy the constructor and all other methods from the superclass description at super to our new class description at self:
下一步是继承。我们一定要把super下的构造函数以及所有基类描述复制到self的类描述中来:
const size_t offset = offsetof(struct Class, ctor); ... memcpy((char *) self + offset, (char *) self —> super + offset, sizeOf(self —> super) — offset);
Assuming that the constructor is the first method in struct Class, we use the ANSI¬C macro offsetof() to determine where our copy is to start. Fortunately, the class description at super is subclassed from Object and has inherited sizeOf() so we can compute how many bytes to copy.
假设构造函数是struct Class的第一个方法,我们用ANSI C的宏offsetof()来确定复制开始的地址。幸运的是,super的类描述是继承Object而来,继承了后者的sizeOf(),因此我们可以计算要复制多少字节。
While this solution is not entirely foolproof, it seems to be the best compro¬mise. Of course, we could copy the entire area at super and store the new name etc. afterwards; however, we would still have to rescue the struct Object at the beginning of the new class description, because new() has already stored the class description’s class description pointer there.
尽管这个方案不完全可靠,它大概是最好的折中。当然,我们可以复制整个super再随后存入新的名字等等。不过这样一来,我们还是不得不在新的类描述的开始处“拯救”struct Object,因为new()方法已经在此保存了类描述的类描述指针。
The last part of the Class constructor is responsible for overwriting whatever methods have been specified in the argument list to new(). ANSI-C does not let us assign function pointers to and from void *, so a certain amount of casting is necessary:
Class构造函数的最后部份负责重写new()变元中指明的方法。ANSI-C不允许将函数指针赋值给void *或者反过来,因此适当的加工是必要的:
{
typedef void (* voidf) (); /* generic function pointer */
voidf selector;
va_list ap = * app;
while ((selector = va_arg(ap, voidf)))
{ voidf method = va_arg(ap, voidf);
if (selector == (voidf) ctor)
* (voidf *) & self —> ctor = method;
else if (selector == (voidf) dtor)
* (voidf *) & self —> dtor = method;
else if (selector == (voidf) differ)
* (voidf *) & self —> differ = method;
else if (selector == (voidf) puto)
* (voidf *) & self —> puto = method;
}
return self;
}}
As we shall see in section 6.10, this part of the argument list is best shared among all class constructors so that the selector/method pairs may be specified in any order. We accomplish this by no longer incrementing * app; instead we pass a copy ap of this value to va_arg().
如同我们将在6.10节所见,变元清单的这一部分由所有类构造函数充分共享,因而选择器/方法可以以任何顺序指定。我们获得如此便利,是因为放弃累加* app,代之以传送该值的副本ap给va_arg()。
Storing the methods in this fashion has a few consequences: If no class con¬structor is interested in a selector, a selector/method pair is silently ignored, but at least it is not added to a class description where it does not belong. If a method does not have the proper type, the ANSI-C compiler will not detect the error because the variable argument list and our casting prevent type checks. Here we rely on the programmer to match the selector to the method supplied with it, but they must be specified as a pair and that should result in a certain amount of plausibility.
以这样的方式存储方法有几个后果:如果类构造函数对选择器不感兴趣,它就忽略选择器/方法配对,不过至少选择器/方法不会被加到它所不属于的类描述。如果一个方法没有恰当的类型,因为可变的变元表以及如上的“加工”阻止了类型检查,ANSI-C编译器将不会报错。我们因此有赖于程序员对选择器和方法互配,而这互配应当在一定程度上是有效合理的。
Initialization 初始化
Normally we obtain a class description by sending new() to a metaclass description. In the case of Class and Object we would issue the following calls:
一般我们给描述类的描述发送一个new()方法,以获得类的描述。对于Class和Object,我们做如下调用:
const void * Object = new(Class, "Object", Object, sizeof(struct Object), ctor, Object_ctor, dtor, Object_dtor, differ, Object_differ, puto, Object_puto, 0); const void * Class = new(Class, "Class", Object, sizeof(struct Class), ctor, Class_ctor, dtor, Class_dtor, 0);
Unfortunately, either call relies on the other already having been completed. There¬fore, the implementation of Class and Object in Object.c requires static initialization of the class descriptions. This is the only point where we explicitly initialize a struct Class:
不幸的是无论哪个调用都有赖于另一个的完成。因此,Object.c中Class和Object的实现要求类描述必须静态初始化。这是唯一显式地初始化struct Class的地方。
static const struct Class object [] = {
{ { object + 1 },
"Object", object, sizeof(struct Object),
Object_ctor, Object_dtor, Object_differ, Object_puto
},
{ { object + 1 },
"Class", object, sizeof(struct Class),
Class_ctor, Class_dtor, Object_differ, Object_puto
}
};
const void * Object = object;
const void * Class = object + 1;
An array name is the address of the first array element and can already be used to initialize components of the elements. We fully parenthesize this initialization in case struct Object is changed later on.
一个数组的名字就是其第一个数组元素的地址,已经可以用来初始化元素的分量。以后当struct Object变动时,我们会经常引用其初始化。
Selectors 选择器
The job of a selector function is unchanged from chapter 2: One argument _self is the object for dynamic linkage. We verify that it exists and that the required method exists for the object. Then we call the method and pass all arguments to it; therefore, the method can assume that _self is a proper object for it. Finally, we return the result value of the method, if any, as the result of the selector. Every dynamically linked method must have a selector. So far, we have hidden calls to the constructor and the destructor behind new() and delete(), but we still need the function names ctor and dtor for the selector/method pairs passed to the Class constructor. We may later decide to bind new() and delete() dynamically; therefore, it would not be a good idea to use their names in place of ctor and dtor.
选择器函数的功能自第二章起没有变化:变元_self是动态连接的对象。我们验证它的存在以及该对象所需方法的存在。然后调用这方法,将所有变元参量传给它。如此,该方法可以假设_self是个适合此方法的对象。最后返回方法的结果值(如果有的话)也即选择器的结果。每个动态连接方法必须有相应的选择器。迄今为止我们隐藏了在new()和delete()后面对构造函数和析构函数的调用,可是我们仍然需要ctor和dtor这样的函数名,用于传给Class构造函数的选择器/方法。以后也许要将new和delete变成动态的,所以在ctor和dtor的位置上不宜用它们的名字。
We have introduced a common superclass Object for all our classes and we have given it some functionality that simplifies implementing selector functions. classOf() inspects an object and returns a non-zero pointer to its class description. This permits the following implementation for delete():
我们已经为所有的类引入了一个公共基类Object并且赋予了它一些功能,简化实现选择器函数。classOf()检视一个对象,返回指向它的类描述的非空指针。这就允许delete()如下的实现:
void delete (void * _self)
{
if (_self)
free(dtor(_self));
}
void * dtor (void * _self)
{ const struct Class * class = classOf(_self);
assert(class —> dtor);
return class —> dtor(_self);
}
new() must be implemented very carefully but it works similarly:
new()方法的实现必须非常小心却也类同:
void * new (const void * _class, ...)
{ const struct Class * class = _class;
struct Object * object;
va_list ap;
assert(class && class —> size);
object = calloc(1, class —> size);
assert(object);
object —> class = class;
va_start(ap, _class);
object = ctor(object, & ap);
va_end(ap);
return object;
}
We verify the class description and we make sure that we can create a zero-filled object. Then we initialize the class description of the object and we are ready to let the normal selector ctor() find and execute the constructor:
我们验证类描述,确保可以创建零值填充的对象。然后,我们初始化对象的类描述,最后万事具备,只等选择器ctor()找出并执行构造函数:
void * ctor (void * _self, va_list * app)
{ const struct Class * class = classOf(_self);
assert(class —> ctor);
return class —> ctor(_self, app);
}
There is perhaps a bit too much checking going on, but we have a uniform and robust interface.
也许检验太多,不过接口是坚固一致的。
Superclass Selectors 基类选择器
Before a subclass constructor performs its own initialization, it is required to call the superclass constructor. Similarly, a subclass destructor must call its superclass destructor after it has completed its own resource reclamation. When we are implementing selector functions, we should also supply selectors for the superclass calls:
在子类构造函数进行其初始化之前,它必须调用基类构造函数。类似地,子类析构函数在它完成其资源释放之后,也要调用基类的析构函数。当实现选择器函数时,我们也要给这些基类函数调用提供选择器:
void * super_ctor (const void * _class,
void * _self, va_list * app)
{ const struct Class * superclass = super(_class);
assert(_self && superclass —> ctor);
return superclass —> ctor(_self, app);
}
void * super_dtor (const void * _class, void * _self)
{ const struct Class * superclass = super(_class);
assert(_self && superclass —> dtor);
return superclass —> dtor(_self);
}
These selectors should only be called by a subclass implementation; therefore, we include their declarations into the representation file and not into the interface file. To be on the safe side, we supply superclass selectors for all dynamically linked methods, i.e., every selector has a corresponding superclass selector. This way, every dynamically linked method has a simple way to call its superclass method.
这些选择器应当只由基类实现调用;故此,我们将其声明放进结构头文件而不是接口头文件中。保险起见,我们给所有动态连接方法提供基类选择器,即每个选择器都有其相应的基类选择器。由此,每个动态连接方法都有一个简单方式调用其基类方法。
Actually, there is a subtle trap luring. Consider how a method of an arbitrary class X would call its superclass method. This is the correct way:
实际上,这里存在一个诡异诱人的陷阱。考虑一个任意类X是如何调用它的基类方法的。正确的方式是:
static void * X_method (void * _self, va_list * app)
{ void * p = super_method(X, _self, app);
...
Looking at the superclass selectors shown above we see that super_method() in this case calls
注意上面的基类选择器,看到super_method()在这里调用
super(X) —> method(_self, app);
i.e., the method in the superclass of the class X for which we just defined X_method(). The same method is still reached even if some subclass Y inherited X_method() because the implementation is independent of any future inheritance.
即我们刚刚定义的类X的基类中的这一方法X_method()。即使某子类Y继承了X_method(),还是得到了同样的方法,因为这个实现是独立于任何未来的继承的。
The following code for X_method() may look more plausible, but it will break once the method is inherited: static void * X_method (void * _self, va_list * app)
下面X_method()的代码实现可能看起来更棒,不过一旦这方法被继承就会出错:
{ void * p = /* WRONG */
super_method(classOf(_self), _self, app);
...
The superclass selector definition now produces
基类选择器定义现在产生
super(classOf(_self)) —> method(_self, app);
If _self is in class X, we reach the same method as before. However, if _self is in a subclass Y of X we get
如果_self属于类X,我们得到与前同样的方法。不过,如果_self属于类X的子类Y,则
super(Y) —> method(_self, app);
and that is still X_method(), i.e., instead of calling a superclass method, we get stuck in a sequence of recursive calls!
仍然是X_method(),即,本是要调用基类函数,结果却陷于一系列的递归调用!
A New Metaclass — PointClass 一个新的元类:PointClass
Object and Class are the root of our class hierarchy. Every class is a subclass of Object and inherits its methods, every metaclass is a subclass of Class and cooperates with its constructor. Any in section 6.4 has shown how a simple sub¬class can be made by replacing dynamically linked methods of its superclass and, possibly, defining new statically linked methods. We now turn to building classes with more functionality. As an example we connect Point and Circle to our class hierarchy. These classes have a new dynamically linked method draw(); therefore, we need a new metaclass to accommodate the link. Here is the interface file Point.h:
Object和Class是我们的类树的起点。所有类都是Object的子类并继承它的方法;所有元类都是Class的子类并与其构造函数协调。6.4节的Any曾经显示如何通过替换其基类的动态连接方法以及——可能的话——定义新的静态连接方法来创建一个简单子类。我们下面转入功能更多的类的建造。作为例子,我们将点Point和圆Circle加到类树中来。这些类有个新的动态连接方法draw()。因此我们需要一个新的元类来容纳这个连接。下面是接口头文件Point.h:
#include "Object.h" extern const void * Point; /* new(Point, x, y); */ void draw (const void * self); void move (void * point, int dx, int dy); extern const void * PointClass; /* adds draw */
The subclass always includes the superclass and defines a pointer to the class description and to the metaclass description if there is a new one. Once we intro¬duce metaclasses, we can finally declare the selector for a dynamically linked method where it belongs: in the same interface file as the metaclass pointer.
子类总是包含了基类,还定义指向类描述的指针以及指向元类描述——如果有新的的话——的指针。一旦引入元类,我们终于能在元类指针所在的同一接口头文件里声明动态连接方法专有的选择器。
The representation file Point.r contains the object structure struct Point with its access macros as before, and it contains the superclass selectors together with the structure for the metaclass:
和此前一样,结构头文件Point.r包含对象结构struct Point及其访问的宏定义,而且它还包含基类选择器与其元类的结构:
#include "Object.r"
struct Point { const struct Object _; /* Point : Object */
int x, y; /* coordinates */
};
#define x(p) (((const struct Point *)(p)) —> x)
#define y(p) (((const struct Point *)(p)) —> y)
void super_draw (const void * class, const void * self);
struct PointClass {
const struct Class _; /* PointClass : Class */
void (* draw) (const void * self);
};
The implementation file Point.c contains move(), Point_draw(), draw(), and super_draw(). These methods are written as before; we saw the technique for the superclass selector in the previous section. The constructor must call the super¬class constructor:
代码实施文件Point.c包含move()、Point_draw()、draw()和super_draw()。这些方法跟以前一样,其技术就是前一节里我们已经看到的基类选择器的技术。构造函数必须调用基类构造函数:
static void * Point_ctor (void * _self, va_list * app)
{ struct Point * self = super_ctor(Point, _self, app);
self —> x = va_arg(* app, int);
self —> y = va_arg(* app, int);
return self;
}
One new idea in this file is the constructor for the metaclass. It calls the super¬class constructor to perform inheritance and then uses the same loop as Class_ctor() to overwrite the new dynamically linked method draw(): static void * PointClass_ctor (void * _self, va_list * app)
这里的一个新想法是关于元类的构造函数。它调用基类构造函数来实现继承,然后使用象Class_ctor()同样的循环重写新的动态连接方法draw():
{ struct PointClass * self
= super_ctor(PointClass, _self, app);
typedef void (* voidf) ();
voidf selector;
va_list ap = * app;
while ((selector = va_arg(ap, voidf)))
{ voidf method = va_arg(ap, voidf);
if (selector == (voidf) draw)
* (voidf *) & self —> draw = method;
}
return self;
}
Note that we share the selector/method pairs in the argument list with the super¬class constructor: ap takes whatever Class_ctor() returns in * app and starts the loop from there.
注意在变元表中我们共享基类构造函数的选择器-方法对偶:ap取值Class_ctor()在指针* app中的返回值,并由此开始循环。
With this constructor in place we can dynamically initialize the new class descriptions: PointClass is made by Class and then Point is made with the class description PointClass:
有了这个构造函数,我们可以动态地初始化新的类描述:由Class初始化PointClass,然后再由PointClass的类描述初始化Point:
void initPoint (void)
{
if (! PointClass)
PointClass = new(Class, "PointClass",
Class, sizeof(struct PointClass),
ctor, PointClass_ctor,
0);
if (! Point)
Point = new(PointClass, "Point",
Object, sizeof(struct Point),
ctor, Point_ctor,
draw, Point_draw,
0);
}
Writing the initialization is straightforward: we specify the class names, inheritance relationships, and the size of the object structures, and then we add selector/method pairs for all dynamically linked methods defined in the file. A zero completes each argument list.
引用初始化是直接了当的:指明类的名称、继承关系、对象结构大小以及所有头文件里定义的动态连接方法的选择器-方法对偶。最后,0标示变元表结束。
In chapter 9 we will perform this initialization automatically. For now, init-Point() is added to the interface in Point.h and the function must definitely be called before we can make points or subclasses. The function is interlocked so that it can be called more than once — it will produce exactly one class description PointClass and Point.
第九章我们将对此初始化加以自动化处理。眼下,我们暂时将initPoint()列入接口头文件Point.h。在我们可以画点或创建其子类之前,这个函数都无疑必须调用。这个函数也是锁定好的,所以可以调用不止一次——它只会产生一个类描述PointClass和Point。
As long as we call initPoint() from main() we can reuse the test program points from section 4.1 and we get the same output:
只要在main()中调用initPoint(),我们就能再用4.1节的测试程序points,而且得到同样的输出结果:
$ points p "." at 1,2 "." at 11,22
Circle is a subclass of Point introduced in chapter 4. In adding it to the class hierarchy, we can remove the ugly code in the constructor shown in section 4.7:
Circle是第四章Point的子类。在将其加入我们总的类树时,我们可以拿掉4.7节展示的构造函数中难看的代码:
static void * Circle_ctor (void * _self, va_list * app)
{ struct Circle * self = super_ctor(Circle, _self, app);
self —> rad = va_arg(* app, int);
return self;
}
Of course, we need to add an initialization function initCircle() to be called from main() before circles can be made:
当然,要增加一个初始化函数initCircle(),给main()在画圆之前调用:
void initCircle (void)
{
if (! Circle)
{ initPoint();
Circle = new(PointClass, "Circle",
Point, sizeof(struct Circle),
ctor, Circle_ctor,
draw, Circle_draw,
0);
}
}
Because Circle depends on Point, we call on initPoint() before we initialize Circle. All of these functions do their real work only once, and we can call them in any order as long as we take care of the interdependence inside the function itself.
因为Circle依赖于Point,所以初始化Circle前先调用initPoint()。所有这些函数的实际功能都只执行一次,只要保证函数自身内部的独立无关性,我们就可以以任意次序调用它们。
Summary 章结
Objects point to their class descriptions which, for the most part, contain pointers to dynamically linked methods. Class descriptions with the same set of method pointers constitute a metaclass — class descriptions are objects, too. A metaclass, again, has a class description.
对象指向它们的类描述,后者大多包括了指向动态连接方法的指针。有着同一组方法指针的类描述构成一个元类——类描述也是对象。同样地,一个元类也有类描述。
Things remain finite because we start with a trivial class Object and with a first metaclass Class which has Object as a superclass. If the same set of methods — constructor, destructor, comparison, and display — can be applied to objects and class descriptions, then the metaclass description Class which describes the class description Object also describes itself.
从一个最简约的类Object和元类Class(前者为后者的基类)出发,现在的局面还不是太复杂。如果这同一组方法——构造函数、析构函数、比较和显示——都能适用于对象和类描述,那么描述类描述的元类描述Class就也描述它自身。
A metaclass constructor fills a class description and thus implements binary inheritance, the destructor returns zero to protect the class description from being destroyed, the display function could show method pointers, etc. Two class descriptions are the same if and only if their addresses are equal.
一个元类构造函数充当起类描述因而实现二元继承,析构函数返回0值以使类描述免于销毁,显示函数能够表示方法指针,等等。当且仅当其地址相同,两个类描述才是同一个类描述
If we add dynamically linked methods such as draw(), we need to start a new metaclass, because its constructor is the one to insert the method address into a class description. The metaclass description always uses struct Class and is, therefore, created by a call
如果要增加动态连接方法,比如draw(),我们要开始一个新的元类,因为它的构造函数才将此方法地址插入到类描述中。元类描述总是用struct Class,所以由如下调用创建:
PointClass = new(Class, ...
ctor, PointClass_ctor,
0);
Once the metaclass description exists, we can create class descriptions in this metaclass and insert the new method:
一旦有了元类描述,就能在此元类创建类描述并且加入新的方法:
Point = new(PointClass, ...
draw, Point_draw,
...
0);
These two calls must be executed exactly once, before any objects in the new class can be created. There is a standard way to write all metaclass constructors so that the selector/method pairs can be specified in any order. More classes in the same metaclass can be created just by sending new() to the metaclass description.
在这个新的类中任何对象创建之前,这两个函数必须调用不多不少恰恰一次。这是写所有元类构造函数的标准方试,这样就可以以任意次序指定选择器/方法对偶。通过传送new()方法给元类描述,就可以创建该元类下更多别的类。
Selectors are also written in a standard fashion. It is a good idea to decide on a discipline for constructors and destructors to always place calls along the super¬class chain. To simplify coding, we provide superclass selectors with the same arguments as selectors; an additional first parameter must be specified as the class for which the method calling the superclass selector is defined. Superclass selec¬tors, too, are written according to a standard pattern.
选择器代码也是标准的。强制构造函数和析构函数总是顺延基类链来调用是个好办法。为简化编码,我们给基类选择器与选择器同样的调用参数。当然,第一个参数是额外的,我们必须用它指定调用方法所属的类。基类选择器也是按照标准模式写成的。
A coherent style of verifications makes the implementations smaller and more robust: selectors verify the object, its class, and the existence of a method; super¬class selectors should additionally verify the new class argument; a dynamically linked method is only called through a selector, i.e., it need not verify its object. A statically linked method is no different from a selector: it must verify its argument object.
检验方法协调一致的风格使得(软件的)实现更小巧而坚固:选择器检验对象、对象的类以及方法的存在;基类选择器应当检验新的类参数;动态连接方法总是通过选择器调用的,也即它不需要检验其所属对象。静态连接方法与选择器没有不同:它必须检验它的参数对象。
Let us review the meaning of two basic components of objects and class descriptions and our naming conventions. Every class eventually has Object as a superclass. Given a pointer p to an object in an arbitrary subclass of Object,the component p−>class points to the class description for the object. Assume that the pointer C points to the same class description and that C is the class name pub¬lished in the interface file C.h. Then the object at p will be represented with a struct C. This explains why in section 6.3 Class−>class has to point to Class itself: the object to which Class points is represented by a struct Class.
让我们回顾一下对象和类描述两个基本成份的意义以及我们的命名约定。每个类最终都以Object为基类。给定指向Object任意子类中对象的指针p,p->class指这对象的类描述。假定指针C指向同一类描述而且C也是接口头文件C.h公布的类名,那么p所指的对象将以struct C代表。这就解释了为什么6.3节中Class->class不得不指向Class自身:Class所指向的对象由struct Class代表。
Every class description must begin with a struct Class to store things like the class name or a pointer to the superclass description. Now let C point to a class description and let C−>super point to the same class description as the pointer S published in the interface file S.h, i.e., S is the superclass of C. In this case, struct C must start with struct S. This explains why in section 6.3 Class−>super has to point to Object: we decided that a struct Class starts with a struct Object.
每个类描述一定以struct Class开头,存储类名或是指向基类描述的指针这样的东西。设C指向一个类描述,C->super指向接口头文件S.h公布的指针S所指向的同一类描述,也就是说S是C的基类,在此情况下struct C一定要以struct S开头。这就解释了为什么6.3节中Class->super不得不指向Object:因为我们决定了struct Class自struct Object始。
The only exception to this rule is the fact that Object−>super has the value Object although it was pointed out in section 6.3 that this was a rather arbitrary decision.
这个规则的唯一例外是Object->super回指Object。在6.3节里已经指出这个安排是一个随意的安排。
