工学1号馆

home

« | 返回首页 | »

C面向对象编程--动态链接与泛型函数1

By Wu Yudong on January 02, 2017

本文打算实现一个简单的string数据类型,对于一个string类型,我们分配一个动态的buffer来保存文本,当string销毁的时候,能够回收buffer。

1、构造函数与析构函数

new() 的责任是创建对象,delete() 必须回收它所拥有的资源。由于 new() 将对象的描述符作为它的第一个参数,所以它知道所创建对象的类型。依靠参数,我们可以使用一系列的 if 判断来单独处理每一个创建,但是缺点是 new() 将显式地包含每一个我们所支持的数据类型,耦合度高。

delete() 也存在很大的问题,必须根据被删除对象的类型采取不同的行动。

我们可以给delete() 一个参数:类型说明符或实现清理功能的函数,但这种方法笨拙和容易出错,有一个更一般且优雅的方式:每个对象都必须知道如何销毁自己的资源。每个对象包含一个指针,通过这个指针可以找到一个具有清除功能的函数。我们称这样的函数为该对象的析构函数。

new() 存在一个问题,它负责创建对象并返回可以传递给 delete() 的指针。例如 new() 必须为每一个对象的析构函数的提供信息,显而易见的方法是使用一个指针指向析构函数,这个析构函数作为传递给 new() 的类型描述的一部分。到目前为止,我们需要以下声明:

struct type {
	size_t size;			/* 对象的大小 */
	void (*dtor) (void *);		/* 析构函数 */
};
struct String {
	char *text;			/* 动态 string */
	const void *destroy;		/* 定位析构函数 */
};
struct Set {
	... information ...
	const void *destroy;		/* 定位析构函数 */
};

初始化是 new() 的工作之一,不同的类型需要不同的工作 -- new() 可能依然为不同的类型需要不同的参数:

new(Set);  /* 新建一个Set */
new(String, "text"); /* 新建一个String */

为了初始化,我们需要一个称为构造函数的特定类型函数,因为构造函数与析构函数都是特定类型且不会改变,我们将它们都传递给 new() 来作为类型描述符的一部分。

注意到构造函数和析构函数都不负责对象自身的请求和释放内存空间 — 这个是 new() 和 delete() 的工作。构造函数被 new() 调用,它的职责仅仅是初始化由 new() 分配的内存。对于一个 string,这牵涉到获取另一块内存来存储text,但是对于struct String自身而言通过 new() 分配,这个空间后来由 delete() 释放。delete() 调用析构函数,这基本上是之前初始化顺序的反转。

2、方法、消息、类和对象

delete() 必须在不知道给定对象类型的情形下定位析构函数,于是修改上面代码中的声明,但是必须坚持将定位析构函数的指针放在所有传递给 delete() 对象的最前面。

这个指针指向什么呢?如果我们有对象的地址,这个指针让我们可以访问对象的特定类型的信息,比如对象的析构函数,接着我们还可以向对象添加其他的特定类型函数,比如显示对象的函数,或者比较函数 differ(),或者创建完整对象拷贝的函数 clone() 。结果我们使用一个指针指向函数指针表。

仔细一看,发现该表必须作为传递给 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;	/* 必须放在最前面 */
	char *text;
};
struct Set {
	const void *class;	/* 必须放在最前面 */
	...
};

我们的每一个对象都是起始于一个指向它自己类型描述的指针,通过这个类型描述我们可以定位对象的特定类型信息:.size 是 new() 为对象分配的长度; .ctor 指向由new() 调用的构造函数,这个函数接收分配的区域和原来传递给new() 的剩余参数列表;.dtor 指向由 delete() 调用的析构函数,这个析构函数接收需要销毁的对象,.clone 指向一个接收待拷贝对象的拷贝函数;.differ 指向对象与其他相比较的函数。

从上面的函数指针表可以看到,每一个函数在被选中的时候为对象服务,只有构造函数必须应付部分初始化的内存区域。我们称这些为对象的方法。调用一个方法被称为消息,并且使用了名为 self 的参数来标记消息的接收对象,由于我们使用普通C函数,self 不必是第一个参数。

很多对象共享相同的类型描述,例如,它们需要相同的内存和提供给它们相同的函数,我们称所有具备相同类型描述的对象为一个类;单个对象称为类的实例。

一个对象是一个类的实例,例如,对象具备代表由 new() 分配的内存的资格,以及维持它所属类的方法的资格,通常来说,一个对象是一个特定数据类型的值。

如果文章对您有帮助,欢迎点击下方按钮打赏作者

Comments

No comments yet.
To verify that you are human, please fill in "七"(required)