OOC:Dynamic Linkage — Generic Functions
来自 ChinaUnix Wiki
动态连接——通用函数(Dynamic Linkage — Generic Functions)
构造与析构(Constructors and Destructors)
Let us implement a simple string data type which we will later include into a set. For a new string we allocate a dynamic buffer to hold the text. When the string is deleted, we will have to reclaim the buffer.
让我们先实现一个简单的字符串数据类型,在后面的章节里,我们会把它放入一个集合中。 在创建一个新的字符串时,我们分配一块动态的缓冲区来保存它所包含的文本。在删除该字符串时,我们需要回收那块缓冲区。
new() is responsible for creating an object and delete() must reclaim the resources it owns. new() knows what kind of object it is creating, because it has the description of the object as a first parameter. Based on the parameter, we could use a chain of if statements to handle each creation individually. The drawback is that new() would explicitly contain code for each data type which we support.
new() 负责创建一个对象,而delete() 必须回收该对象所占用的资源。new() 知道它要创建的对象是什么类型的,因为它的第一个参数为该对象的描述符。依据该参数,我们可以用一系列if语句来分别处理每一种数据类型的对象的创建。这种做法的的缺点是,对我们所要支持的每一种数据类型,new()中都要显式地包含特定于该数据类型的代码(译者注:即,需要将数据类型的信息硬编码到new()中)。
delete(), however, has a bigger problem. It, too, must behave differently based on the type of the object being deleted: for a string the text buffer must be freed; for an object as used in chapter 1 only the object itself has to be reclaimed; and a set may have acquired various chunks of memory to store references to its elements.
delete() 所要解决的问题更为棘手。它也必须随着被删除对象的类型的不同而作出不同的动作:若是一个String 对象,则必须释放它的文本缓冲区;若是在第一章中用过的那种Object 对象,则只需回收该对象自身;而若是一个Set 对象,则需要考虑它可能已经请求了很多内存块用来储存其元素的引用。
We could give delete() another parameter: either our type descriptor or the function to do the cleaning up, but this approach is clumsy and error-prone. There is a much more general and elegant way: each object must know how to destroy its own resources. Part of each and every object will be a pointer with which we can locate a clean-up function. We call such a function a destructor for the object.
我们可以给delete() 添加一个参数:类型描述符或者做清理工作的函数,但这种方式不仅笨拙,而且容易出错。有一种更为通用更为优雅的方式,即保证每个对象都知道如何去销毁它所占有的资源。可以让每个对象都存有一个指针域,用它可以定位到一个清理函数。我们称这种函数为该对象的析构函数。
Now new() has a problem. It is responsible for creating objects and returning pointers that can be passed to delete(), i.e., new() must install the destructor information in each object. The obvious approach is to make a pointer to the destructor part of the type descriptor which is passed to new(). So far we need something like the following declarations:
现在new() 有一个问题。它负责创建对象并返回一个能传递给delete() 的指针,就是说,new() 必须配置每个对象中的析构函数信息。很容易想到的办法,是让指向析构函数的指针成为传递给new() 的类型描述符的一部分。到目前为止,我们需要的东西类似如下声明:
struct type {
size_t size; /* size of an object */
void (* dtor) (void *); /* destructor */
};
struct String {
char * text; /* dynamic string */
const void * destroy; /* locate destructor */
};
struct Set {
... information ...
const void * destroy;
};
It looks like we have another problem: somebody needs to copy the destructor pointer dtor from the type description to destroy in the new object and the copy may have to be placed into a different position in each class of objects.
看起来我们有了另一个问题:需要有人把析构函数的指针dtor 从类型描述符中拷贝到新对象的 destory 域,并且该副本在每一类对象中的位置可能还不尽相同。
Initialization is part of the job of new() and different types require different work — new() may even require different arguments for different types:
初始化是new() 工作的一部分,不同的类型有不同的事情要做——new() 甚至需要为不同的类型而配备不同的参数列表:
new(Set); /* make a set */ new(String, "text"); /* make a string */
For initialization we use another type-specific function which we will call a constructor. Since constructor and destructor are type-specific and do not change, we pass both to new() as part of the type description.
对于初始化,我么使用另一种特定于类型(与类型有绑定性质)的函数,我们称之为构造函数。由于构造函数和析构函数都是特定于类型的,不会改变,我们把他们两个都作为类型描述的一部分传递给new() 。
Note that constructor and destructor are not responsible for acquiring and releasing the memory for an object itself — this is the job of new() and delete(). The constructor is called by new() and is only responsible for initializing the memory area allocated by new(). For a string, this does involve acquiring another piece of memory to store the text, but the space for struct String itself is allocated by new(). This space is later freed by delete(). First, however, delete() calls the destructor which essentially reverses the initialization done by the constructor before delete() recycles the memory area allocated by new().
要注意的是,构造函数和析构函数不负责请求和释放该对象自身所需的内存,这是new() 和delete()的工作。构造函数由new() 调用,只负责初始化new() 分配的内存区域。对于一个字符串来说,构造函数做初始化工作时确实需要申请一块内存来存放文本,但struct String 自身所占空间是由new() 分配的。这块空间最后会被 delete() 释放。而首先要做的是,delete() 调用析构函数,做与构造函数的初始化相逆的工作,然后才是delete() 回收new() 所分配的内存区域。
方法、消息、类与对象(Methods, Messages, Classes and Objects)
delete() must be able to locate the destructor without knowing what type of object it has been given. Therefore, revising the declarations shown in section 2.1, we must insist that the pointer used to locate the destructor must be at the beginning of all objects passed to delete(), no matter what type they have.
delete()必须能够在不知所给对象类型的情况下定位到析构函数。因此,需要修订第2.1章节中的声明,对于所有传入delete()的对象,强调用于定位析构函数的指针必须位于这个对象的头部,而不管这些对象具体是什么类型。
What should this pointer point to? If all we have is the address of an object, this pointer gives us access to type-specific information for the object, such as its destructor function. It seems likely that we will soon invent other type-specific functions such as a function to display objects, or our comparison function differ(), or a function clone() to create a complete copy of an object. Therefore we will use a pointer to a table of function pointers.
这个指针又应该指向什么呢?如果我们有的只是一个对象的地址,那么这个指针可让我们访问这个对象的类型信息,诸如析构函数。这样看起来我们同样也将很快建立一个其他的类型信息函数,诸如显示对象的函数,或者比较函数differ(),又或者可以创建本对象完整拷贝的clone()函数。因此我将让这个指针指向一个函数指针表。
Looking closely, we realize that this table must be part of the type description passed to new(), and the obvious solution is to let an object point to the entire type description:
如此看来,我们认识到这个指针索引表必须是类型描述的一部分,并且传给new(),并且显而易见的解决方式便是把整个的类型描述作为一个对象,如下所示:
struct Class {
size_t size;
void * (* ctor) (void * self, va_list * app);
void * (* dtor) (void * self);
void * (* clone) (const void * self);
int (* differ) (const void * self, const void * b);
};
struct String {
const void * class; /* must be first */
char * text;
};
struct Set {
const void * class; /* must be first */
...
};
Each of our objects starts with a pointer to its own type description, and through this type description we can locate type-specific information for the object: .size is the length that new() allocates for the object; .ctor points to the constructor called by new() which receives the allocated area and the rest of the argument list passed to new() originally; .dtor points to the destructor called by delete() which receives the object to be destroyed; .clone points to a copy function which receives the object to be copied; and .differ points to a function which compares its object to something else.
我们的每一个对象开始于一个指向它自身所拥有的类型描述的指针,并且通过这个类型描述,我们能定位这个对象类型描述信息:.size是通过new()分配的这个对象的长度;.ctor指针指向被new()函数调用的构造器,这个构造器接受被申请的区域和在初始时传递给new()的其余的参数列表;.dtor指向被delete()调用的析构器,用来销毁接受到的对象;.clone指向一个拷贝函数,用来拷贝接受到的对象;.differ指针指向一个用来将这个对象于其他对象进行比较的函数。
Looking down this list, we notice that every function works for the object through which it will be selected. Only the constructor may have to cope with a partially initialized memory area. We call these functions methods for the objects. Calling a method is termed a message and we have marked the receiving object of the message with the parameter name self. Since we are using plain C functions, self need not be the first parameter.
大体上看看上面这个函数列表就能发现每个函数都是通过对象来选择做用于不同的对象的。只有构造函数要处理那些只部分初始化的存储区域。我们称这些函数叫做这些对象的方法。调用一个方法就叫做一次消息,我们已经用self作为函数参数来标记接收该消息的对象。当然这里我们用的是C函数,所以self不一定得是函数的头一个参数。
Many objects will share the same type descriptor, i.e., they need the same amount of memory and the same methods can be applied to them. We call all objects with the same type descriptor a class; a single object is called an instance of the class. So far a class, an abstract data type, and a set of possible values together with operations, i.e., a data type, are pretty much the same.
一些对象将共享同样类型描述,例如,他们需要同样数量的内存和提供同样的方法供使用。我们称所有拥有同样类型描述的对象为一个类;单独的一个对象称作为这个类的实例。到现在为止,一个抽象数据类型,一些可能的值及其的一些操作,例如一个的数据类型已经更加相似于一个类了。
An object is an instance of a class, i.e., it has a state represented by the memory allocated by new() and the state is manipulated with the methods of its class. Conventionally speaking, an object is a value of a particular data type.
一个对象是一个类的实例,也就是说,在通过new()为它分配了内存后,它就有了一个状态,并且这个状态可以通过它所属类的方法进行操作。按惯例,一个对象是一个特定数据类型的一个值。
选择器、动态连接与多态(Selectors, Dynamic Linkage, and Polymorphisms)
Who does the messaging? The constructor is called by new() for a new memory area which is mostly uninitialized:
谁来传递消息?构造函数被new()调用来处理几乎没初始化的内存区。
void * new (const void * _class, ...)
{
const struct Class * class = _class;
void * p = calloc(1, class —> size);
assert(p);
* (const struct Class **) p = class;
if (class —> ctor)
{ va_list ap;
va_start(ap, _class);
p = class —> ctor(p, & ap);
va_end(ap);
}
return p;
}
The existence of the struct Class pointer at the beginning of an object is extremely important. This is why we initialize this pointer already in new():
把类指针放在一个对象的开始是非常重要的,这也是为什么初始化这个已经在new()中的指针。
The type description class at the right is initialized at compile time. The object is
created at run time and the dashed pointers are then inserted. In the assignment
* (const struct Class **) p = class;
p points to the beginning of the new memory area for the object. We force a conversion of p which treats the beginning of the object as a pointer to a struct Class and set the argument class as the value of this pointer.
上图右方的描述数据类型的类是在编译的时候初始化的,对象则是在运行时被创建,此时才将指针安入到其中。在以下的赋值中* (const struct Class **) p = class;p指向该对象的内存区的开始处。我们对p强制类型转换,使之作为Class结构体的指针,并且设置参数的类的指针作为这个指针的值。(指针的指针的*作为变量接受入口参数传递的类的指针。译者注)
Next, if a constructor is part of the type description, we call it and return its result as the result of new(), i.e., as the new object. Section 2.6 illustrates that a clever constructor can, therefore, decide on its own memory management.
随后,如果在这个类型描述里有构造函数,我们调用它并以它的返回值作为new()的返回值,即作为new出的对象。2.6节会说明一个聪明的构造函数能够对自身的内存进行管理。
Note that only explicitly visible functions like new() can have a variable parameter list. The list is accessed with a va_list variable ap which is initialized using the macro va_start() from stdarg.h. new() can only pass the entire list to the constructor; therefore, .ctor is declared with a va_list parameter and not with its own variable parameter list. Since we might later want to share the original parameters among several functions, we pass the address of ap to the constructor — when it returns, ap will point to the first argument not consumed by the constructor.
要注意的是,只有像new()那样明确可见的函数能有变参数列表。用文件stdarg.h里的宏va_start()对va_list类型的变量ap初始化后,就可以通过变量ap来访问这个变参列表。new()只能把整个列表传递给构造函数。因此,.ctor声明使用一个va_list参数,而不是它自己的变参列表。由于我们可能在以后想要在几个函数间共享这些原始参数,因此我把ap的地址传递给构造函数——当它返回后ap将指向变参列表中第一个没有被构造函数使用的参数。
delete() assumes that each object, i.e., each non-null pointer, points to a type description. This is used to call the destructor if any exists. Here, self plays the role of p in the previous picture. We force the conversion using a local variable cp and very carefully thread our way from self to its description:
delete()假定每个对象,即每个非空指针,指向一个类型描述对象。这是为了在有析构函数时方便调用它。在这里,参数self扮演着前面图中的指针p的角色。我们利用本地变量cp进行强制类型转换,并且非常小心的从self找到它的类型描述:
void delete (void * self)
{ const struct Class ** cp = self;
if (self && * cp && (* cp) —> dtor)
self = (* cp) —> dtor(self);
free(self);
}
The destructor, too, gets a chance to substitute its own pointer to be passed to
free() by delete(). If the constructor decides to cheat, the destructor thus has a
chance to correct things, see section 2.6. If an object does not want to be deleted,
its destructor would return a null pointer.
析构器通过被delete()调用也获得一个机会通过替换它自身的指针并传给free()。如果在开始构造器被错误蒙混过去,析构器因而也有机会去更正这个问题,更多讲述见2.6节。如果一个对象不想被删除,它的析构函数可以返回一个空指针。
All other methods stored in the type description are called in a similar fashion. In each case we have a single receiving object self and we need to route the method call through its descriptor:
所有其他存储在类型描述中的方法都以类似的方式被调用。每个方法都有一个参数self用来接受对象,并且需要通过这个描述符调用这个方法。
int differ (const void * self, const void * b)
{
const struct Class * const * cp = self;
assert(self && * cp && (* cp) —> differ);
return (* cp) —> differ(self, b);
}
The critical part is, of course, the assumption that we can find a type description pointer * self directly underneath the arbitrary pointer self. For the moment at least, we guard against null pointers. We could place a ‘‘magic number’’ at the beginning of each type description, or even compare * self to the addresses or an address range of all known type descriptions, but we will see in chapter 8 that we can do much more serious checking.
同样核心部分仍然也是假定我们能找到一个类型描述指针self指向下面任意指针条目。至少当前我们会提防空指针。我们可以是指定一个幻数于每个类型描述的开始,或者甚至用*self与所有已知的类型描述的地址或地址范围进行比较。第8章,我们将做更多的严肃的检测。(入参检测问题)
In any case, differ() illustrates why this technique of calling functions is called dynamic linkage or late binding: while we can call differ() for arbitrary objects as long as they start with an appropriate type description pointer, the function that actually does the work is determined as late as possible — only during execution of the actual call, not before.
无论如何,differ()的示例解释为什么这个调用函数的技术被成为动态联接或后绑定:我们能为任意对象调用differ()只要他们开始于一个适当的类型描述指针,并且这个函数是在被调用之前才尽可能晚的绑定上。
We will call differ() a selector function. It is an example of a polymorphic func- tion, i.e., a function that can accept arguments of different types and act differently on them based on their types. Once we implement more classes which all contain .differ in their type descriptors, differ() is a generic function which can be applied to any object in these classes.
我们称differ()为一个选择函数(工厂?)这是一个多态函数的例子,例如,一个函数能接受不同类型的参数,并且根据他们类型表现出不同的行为。一旦我们实现更多类都含有.differ在他们的类型描述符中,differ()便成为一个通用函数能适用于任何对象。
We can view selectors as methods which themselves are not dynamically linked but still behave like polymorphic functions because they let dynamically linked functions do their real work.
我们可以认为选择器是不动态链接但仍然被认为行为类似多态函数的方法,这是因为他们是在真正工作的时候才进行动态链接。
Polymorphic functions are actually built into many programming languages, e.g., the procedure write() in Pascal handles different argument types differently, and the operator + in C has different effects if it is called for integers, pointers, or floating point values. This phenomenon is called overloading: argument types and the operator name together determine what the operator does; the same operator name can be used with different argument types to produce different effects.
多态函数通常编译在很多程序语言之中,例如在Pascal中write()函数处理不同的参数类型,c中的加法操作符能被调用使用在整型、指针或浮点指针上。这个多态被重载调用:参数类型和操作符名共同决定什么杨的操作被执行;同样的操作符名能同不同参数类型产生不同的操作效果。
There is no clear distinction here: because of dynamic linkage, differ() behaves like an overloaded function, and the C compiler can make + act like a polymorphic function — at least for the built-in data types. However, the C compiler can create different return types for different uses of the operator + but the function differ() must always have the same return type independent of the types of its arguments.
这里没有完全清晰的差别:由于动态链接的模式,至少对于内置的数据类型,differ()的行为如同一个重载函数,并且C编译器能产生如同加号产生的那样的多态函数。但是C编译器能根据不同的加号操作符返回创建不同的返回类型,但函数differ()必须总是有同入参同样的返回类型。
Methods can be polymorphic without having dynamic linkage. As an example, consider a function sizeOf() which returns the size of any object:
方法可以在没有进行动态链接的情况下成为多态的。看如下例子,构造一个函数sizeOf()返回任何一个对象的大小:
size_t sizeOf (const void * self)
{ const struct Class * const * cp = self;
assert(self && * cp);
return (* cp) —> size;
}
All objects carry their descriptor and we can retrieve the size from there. Notice the
difference:
所有的对象都有他们的描述符,并且我们可以从描述符中获得size的大小,注意他们的区别;
void * s = new(String, "text"); assert(sizeof s != sizeOf(s));
sizeof is a C operator which is evaluated at compile time and returns the number of bytes its argument requires. sizeOf() is our polymorphic function which at run time returns the number of bytes of the object, to which the argument points.
sizeof 是一个C的操作符,这个操作符被用来在编译时计算并返回他的参数的字节个数。sizeOf()是我们多态函数在运行时返回参数对象的字节个数。
一个应用(An Application)
While we have not yet implemented strings, we are still ready to write a simple test program. String.h defines the abstract data type:
extern const void * String;
All our methods are common to all objects; therefore, we add their declarations to the memory management header file new.h introduced in section 1.4:
void * clone (const void * self); int differ (const void * self, const void * b); size_t sizeOf (const void * self);
The first two prototypes declare selectors. They are derived from the corresponding components of struct Class by simply removing one indirection from the declarator. Here is the application:
#include "String.h"
#include "new.h"
int main ()
{ void * a = new(String, "a"), * aa = clone(a);
void * b = new(String, "b");
printf("sizeOf(a) == %u\n", sizeOf(a));
if (differ(a, b))
puts("ok");
if (differ(a, aa))
puts("differ?");
if (a == aa)
puts("clone?");
delete(a), delete(aa), delete(b);
return 0;
}
We create two strings and make a copy of one. We show the size of a String object — not the size of the text controlled by the object — and we check that two different texts result in different strings. Finally, we check that a copy is equal but not identical to its original and we delete the strings again. If all is well, the program will print something like
sizeOf(a) == 8 ok
一个实现——String(An Implementation — String)
We implement strings by writing the methods which need to be entered into the type description String. Dynamic linkage helps to clearly identify which functions need to be written to implement a new data type.
我们用编写需要被加入到String这个类型说明中去的方法来实现字符串。动态连接有助于 明确指定需要编写哪些函数来实现一个新的数据类型。
The constructor retrieves the text passed to new() and stores a dynamic copy in the struct String which was allocated by new():
构造函数的文本从传向new()的文本中得到,并保存一份动态拷贝于由new() 分配的String结构体中。
struct String {
const void * class; /* must be first */
char * text;
};
static void * String_ctor (void * _self, va_list * app)
{ struct String * self = _self;
const char * text = va_arg(* app, const char *);
self —> text = malloc(strlen(text) + 1);
assert(self —> text);
strcpy(self —> text, text);
return self;
}
In the constructor we only need to initialize .text because new() has already set up .class.
在构造函数中我们只需要初始化.text,因为.class已经由new()设置。
The destructor frees the dynamic memory controlled by the string. Since delete() can only call the destructor if self is not null, we do not need to check things:
析构函数释放由字符串控制的动态内存。由于只有在self非空的情况下delete() 才会调用析构函数,因此在这里我们不需要检查其他事情:
static void * String_dtor (void * _self)
{ struct String * self = _self;
free(self —> text), self —> text = 0;
return self;
}
String_clone() makes a copy of a string. Later both, the original and the copy, will be passed to delete() so we must make a new dynamic copy of the string’s text. This can easily be done by calling new():
String_clone()复制一份字符串。由于之后初始的和复制的字符串都将被传送到 delete(),所以我们必须产生一份新的字符串的动态拷贝。这只要简单的调用new() 即可。
static void * String_clone (const void * _self)
{ const struct String * self = _self;
return new(String, self —> text);
}
String_differ() is certainly false if we look at identical string objects and it is true if we compare a string with an entirely different object. If we really compare two distinct strings, we try strcmp():
String_differ()在比较两个同一的字符串对象时返回false,如果比较的时两个完全不同的 对象,则返回true。如果我们比较的是两个不同的字符串,则使用strcmp():
static int String_differ (const void * _self, const void * _b)
{ const struct String * self = _self;
const struct String * b = _b;
if (self == b)
return 0;
if (! b || b —> class != String)
return 1;
return strcmp(self —> text, b —> text);
}
Type descriptors are unique — here we use that fact to find out if our second argument really is a string.
类型描述符是唯一的--在这里我们用这个事实来验证第二个参数是否真的是一个字符串。
All these methods are static because they should only be called through new(), delete(), or the selectors. The methods are made available to the selectors by way of the type descriptor:
这些方法都被设置成静态是因为他们需要被new(),delete()或者其他的选择器调用。 这些方法通过类型描述符使得他们可以被选择器调用。
#include "new.r"
static const struct Class _String = {
sizeof(struct String),
String_ctor, String_dtor,
String_clone, String_differ
};
const void * String = & _String;
String.c includes the public declarations in String.h and new.h. In order to properly initialize the type descriptor, it also includes the private header new.r which contains the definition of the representation for struct Class shown in section 2.2.
String.c包含了String.h和new.h中的公共声明。为了能够合适的初始化类型描述符,它 也包含了私有头文件new.r,new.r中包括了在2.2节中说明的struct Class的定义。
另一种实现——Atom(Another Implementation — Atom)
To illustrate what we can do with the constructor and destructor interface we implement atoms. An atom is a unique string object; if two atoms contain the same strings, they are identical. Atoms are very cheap to compare: differ() is true if the two argument pointers differ. Atoms are more expensive to construct and destroy: we maintain a circular list of all atoms and we count the number of times an atom is cloned:
为了举例说明我们能使用构造器和析构器接口能做什么,我们实现了Atoms .每个Atom是一个唯一的字符串对象;如果两个Atom包含同样的字符串,那么他们是一样的。Atom是非常容易比较的:如果两个参数指针不同则differ()为true.Atoms的构造于析构的代价更高:我们为所有的atoms维护了一个循环队列,并且我们当atom克隆时就算数量:
struct String {
const void * class; /* must be first */
char * text;
struct String * next;
unsigned count;
};
static struct String * ring; /* of all strings */
static void * String_clone (const void * _self)
{ struct String * self = (void *) _self;
++ self —> count;
return self;
}
Our circular list of all atoms is marked in ring, extends through the .next component, and is maintained by the string constructor and destructor. Before the constructor saves a text it first looks through the list to see if the same text is already stored. The following code is inserted at the beginning of String_ctor():
我们所有的atoms循环列表是在环中被标志的,通过.next成员进行扩展,并且被sting的构造器于析构器进行委会。在构造器保存一个文本之前,他先遍历表内是否有同样的text已经存在。下段代码是插入在字符串构造器String_ctor()之前的。
if (ring)
{ struct String * p = ring;
do
if (strcmp(p —> text, text) == 0)
{ ++ p —> count;
free(self);
return p;
}
while ((p = p —> next) != ring);
}
else
ring = self;
self —> next = ring —> next, ring —> next = self;
self —> count = 1;
If we find a suitable atom, we increment its reference count, free the new string
object self and return the atom p instead. Otherwise we insert the new string
object into the circular list and set its reference count to 1.
如果我们找到了一个匹配的atom,我们将累加引用计数,并释放新的字符串自身,随后返回atom p。否则我们将新的字符串对象插入到循环队列中,并设置引用计数为1.
The destructor prevents deletion of an atom unless its reference count is decremented to zero. The following code is inserted at the beginning of String_dtor():
直到atom的引用计数为0时,析构器才释放删除这个atom.之后的代码是插入到析构器String_dtor()之前的:
if (—— self —> count > 0)
return 0;
assert(ring);
if (ring == self)
ring = self —> next;
if (ring == self)
ring = 0;
else
{ struct String * p = ring;
while (p —> next != self)
p = p —> next;
{
assert(p != ring);
}
p —> next = self —> next;
}
If the decremented reference count is positive, we return a null pointer so that delete() leaves our object alone. Otherwise we clear the circular list marker if our string is the last one or we remove our string from the list.
如果引用计数是整数,我们返回一个空指针使delete()不处理我们的对象。否则我们清除循环链表的标志,如果我们的string是最后一次引用,我们将从链表中删除这个字符串对象。
With this implementation our application from section 2.4 notices that a cloned string is identical to the original and it prints
通过这种方式实现我们的程序,注意克隆出字符串同样是最初的值
sizeOf(a) == 16 ok clone?
概要(Summary)
Given a pointer to an object, dynamic linkage lets us find type-specific functions: every object starts with a descriptor which contains pointers to functions applicable to the object. In particular, a descriptor contains a pointer to a constructor which initializes the memory area allocated for the object, and a pointer to a destructor which reclaims resources owned by an object before it is deleted.
给定一个指向一个对象的指针,动态链接让我们找到类型详细而精确的函数:每个对象开始于一个描述符,这个描述符包含一系列指向对象可用的函数。特别是一个描述符包含一个指针指向构造器用来初始化对象的内存区;一个指针指向析构器用来在对象被删除前归还对象的资源。
We call all objects sharing the same descriptor a class. An object is an instance of a class, type-specific functions for an object are called methods, and messages are calls to such functions. We use selector functions to locate and call dynamically linked methods for an object.
我们成所有对象共享同样的描述符为一个类。一个对象是类的一个实例,类型详细而精确的函数被称作对象的方法,消息调用这些方法。我们使用选择函数为对象来定位并动态链接调用方法。
Through selectors and dynamic linkage the same function name will take different actions for different classes. Such a function is called polymorphic.
通过选择器动态链接不同类的相同的函数名,但会带来不同的结果。这样的函数被称作动态函数。
Polymorphic functions are quite useful. They provide a level of conceptual abstraction: differ() will compare any two objects — we need not remember which particular brand of differ() is applicable in a concrete situation. A cheap and very convenient debugging tool is a polymorphic function store() to display any object on a file descriptor.
动态函数是非常有用的。他们提供一个概念上的抽象:differ()将比较任意两个对象,我们不需要记住哪个differ()的细节标志在具体情况下是适用的。一个低成本并且十分方便的debug工具是多态函数store(),用来显示任何一个在文件描述符上的对象。
练习(Exercises)
To see polymorphic functions in action we need to implement Object and Set with dynamic linkage. This is difficult for Set because we can no longer record in the set elements to which set they belong.
为了查看多态函数的行为,我需要实现对象并通过动态链接进行Set。对于Set这是有难度的,因为我们不再记录这些元素是由谁来设置。(待修改)
There should be more methods for strings: we need to know the string length, we want to assign a new text value, we should be able to print a string. Things get interesting if we also deal with substrings.
英雌需应该需要更多的方法来做这件事情:我们需要知道字符串长度,我们想要指派一个新的文本长度,我们应该能打印字符串。如果我们想做,将会有很多有趣的事情。
Atoms are much more efficient, if we track them with a hash table. Can the value of an atom be changed?
如果我们通过哈希表跟踪原子性的增加是更加有效率的。那么这些值能否被原子的更改?
String_clone() poses an subtle question: in this function String should be the same value as self −> class. Does it make any difference what we pass to new()?
String_clone() 造成了一个敏感的问题:在这个函数String应该同self->class用样的值。这样是否会造成我们传递给new()的参数会有些不同?

