struct foo_object 的定义中,必然要记录 i_foo 的接口指针和 data 数据指针。从 c++ 的观点看,foo_object是基类,它也会有一些基类成员和非虚的成员函数。具体的派生类在实现时,改写了虚表i_foo 的内容(重载了虚函数)。data 数据是在对基类foo_object 继承时扩展的数据成员。但,在这里,我们使用了组合的方式来扩展成员。这增加了一层间接性,但提供了更低的耦合。其中的优劣暂且不讨论了。
通常看起来会是这样: struct foo_object { struct i_foo * vtbl; void * data; void * others; };
void
foo_dosomething(struct foo_object *fobj) {
fobj-vtbl-foobar(fobj-data); // do something else }
此处还有另一个问题:data 的生命期该由谁来负责?
生命期管理是个很大的课题。也是大多数使用 c/c++ 开发的软件的复杂度重要来源。我个人倾向于把生命期管理独立出来解决。所以 foo_object 模块一般并不负责 data 的生命期管理。它只负责struct foo_object 的资源释放。
自己经营自己,是我的 c 语言软件开发的观点之一。我倾向于采用混合语言编程来更好的解决这个问题。比如 c 和 lua ,或者 c 和 c++ 。如果不采用混合语言编程,那么也可以在之后,增加一个同样用 c 语言编写的层次来管理。这个话题,留到下次来讲。 剥离出生命期管理,代码量可以减少很多,也不容易犯错误。
ps. c 语言是一个弱类型的语言。至少比 c++ 要弱一些。这表现在: void * 在 c 语言中可以指代任意数据指针。你可以把任意数据指针赋值给一个 void * 变量,也可以把一个 void * 变量赋给特定的指针类型变量。(这在 c++ 中不推荐,并会被编译器警告)
c 语言中的函数指针也比较有趣。通常,不同类型的函数指针相互赋值是会引起编译器警告的(类型不同)。当然,我们可以用一个 void * 来解决问题。但有时候,我们期望让类型检查严格一些,至
少我们不希望把一个数据指针赋值给一个函数指针。但希望编译器不要理会函数参数的差异。