LW_OOPC宏配置及使用指南
金永华
在lw_oopc.h中,有这么几行代码:
// 配置宏(两种配置选其一):
#define LW_OOPC_USE_STDDEF_OFFSETOF // 表示使用C标准定义的offsetof
// #define LW_OOPC_USE_USER_DEFINED_OFFSETOF // 表示使用用户自定义的lw_oopc_offsetof宏
// 是否支持内存泄露检测,缺省不支持
// #define LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR
// 是否支持调试信息打印(内存分配和释放的详细信息),缺省关闭打印
// #define LW_OOPC_PRINT_DEBUG_INFO
从上边的注释,我们可以看出,LW_OOPC需要使用offsetof宏,如果你的开发环境能够支持C标准定义的offsetof宏,那么什么都不需要动。如果你的开发环境不能支持C标准定义的offsetof宏,那么可以选择使用用户自定义的lw_oopc_offsetof宏(如果你的开发环境连用户自定义的offsetof宏都不支持,在这种情形下,LW_OOPC将无法很好的支持多态特性,很遗憾,你只能与LW_OOPC失之交臂)。
INTERFACE(IMoveable)
{
void (*move)(IMoveable* t); // Move行为
};
在LW_OOPC中,声明接口、抽象类和具体类的方法成员比较特殊,均是函数指针类型的成员。事实上,LW_OOPC正是借助了函数指针的特性,完成了多态功能的模拟。
ABS_CLASS用于声明抽象类,譬如:
ABS_CLASS(Animal)
{
char name[128]; // 动物的昵称(假设小于128个字符)
int age; // 动物的年龄
void (*setName)(Animal* t, const char* name); // 设置动物的昵称
void (*setAge)(Animal* t, int age); // 设置动物的年龄
void (*sayHello)(Animal* t); // 动物打招呼
void (*eat)(Animal* t); // 动物都会吃(抽象方法,由子类实现)
void (*breathe)(Animal* t); // 动物都会呼吸(抽象方法,由子类实现)
void (*init)(Animal* t, const char* name, int age); // 初始化昵称和年龄
};
CLASS用于声明具体类,譬如:
CLASS(Fish)
{
EXTENDS(Animal); // 继承Animal抽象类
IMPLEMENTS(IMoveable); // 实现IMoveable接口
void (*init)(Fish* t, const char* name, int age); // 初始化昵称和年龄
};
在该例中,我们声明了Fish类,并让该类继承Animal抽象类,并且实现IMoveable接口。
在介绍CLASS宏的时候,我们在代码中看到有两个宏:EXTENDS和IMPLEMENTS,如果你查看lw_oopc.h的源码,你将会发现他们是一模一样的:
#define IMPLEMENTS(type) struct type type
#define EXTENDS(type) struct type type
之所以同时提供继承和实现关键字,仅仅是为了让熟悉Java的人更加容易理解LW_OOPC宏。(注意,在LW_OOPC中,建议将继承和实现声明写在结构体的开头,把继承和实现声明摆在显眼的位置,有助于阅读代码的人更好的理解代码)
ABS_CTOR和END_ABS_CTOR用于定义抽象类的构造函数,例如:
/* 设置动物的昵称*/
void Animal_setName(Animal* t, const char* name)
{
// 这里假定name不会超过128个字符,为简化示例代码,不做保护(产品代码中不要这样写)
strcpy(t->name, name);
}
/* 设置动物的年龄*/
void Animal_setAge(Animal* t, int age)
{
t->age = age;
}
/* 动物和我们打招呼*/
void Animal_sayHello(Animal* t)
{
printf("Hello! 我是%s,今年%d岁了!\n", t->name, t->age);
}
/* 初始化动物的昵称和年龄*/
void Animal_init(Animal* t, const char* name, int age)
{
t->setName(t, name);
t->setAge(t, age);
}
ABS_CTOR(Animal)
FUNCTION_SETTING(setName, Animal_setName);
FUNCTION_SETTING(setAge, Animal_setAge);
FUNCTION_SETTING(sayHello, Animal_sayHello);
FUNCTION_SETTING(init, Animal_init);
END_ABS_CTOR
前面,我们声明Animal是一个抽象类,对应的构造函数定义需要使用ABS_CTOR和END_ABS_CTOR。ABS_CTOR是Abstract Constructor的缩写。
在介绍ABS_CTOR和END_ABS_CTOR宏的时候,我们在代码中又发现一个陌生的宏:
FUNCTION_SETTING,这个宏在LW_OOPC中的地位非同凡响,没有它,LW_OOPC就不可能存在。LW_OOPC中的CTOR系列宏(CTOR/END_CTOR,ABS_CTOR/END_ABS_CTOR)除了给对象(在C语言中是struct)分配内存,然后,最重要的一个步骤是为结构体中的函数指针成员赋值,这一过程,也可以称为函数绑定(有点类似C++中的动态联编)。函数绑定的过程由FUNCTION_SETTING宏来完成。
我们来看看FUNCTION_SETTING宏是如何实现的:
#define FUNCTION_SETTING(f1, f2) cthis->f1 = f2;
看到这里,想必读者应该会心一笑了。:)
CTOR和END_CTOR用于定义具体类的构造函数,例如:
/* 鱼的吃行为 */
void Fish_eat(Animal* t)
{
printf("鱼吃水草!\n");
}
/* 鱼的呼吸行为 */
void Fish_breathe(Animal* t)
{
printf("鱼用鳃呼吸!\n");
}
/* 鱼的移动行为 */
void Fish_move(IMoveable* t)
{
printf("鱼在水里游!\n");
}
/* 初始化鱼的昵称和年龄 */
void Fish_init(Fish* t, const char* name, int age)
{
Animal* animal = SUPER_PTR(t, Animal);
animal->setName(animal, name);
animal->setAge(animal, age);
}
CTOR(Fish)
SUPER_CTOR(Animal);
FUNCTION_SETTING(Animal.eat, Fish_eat);
FUNCTION_SETTING(Animal.breathe, Fish_breathe);
FUNCTION_SETTING(IMoveable.move, Fish_move);
FUNCTION_SETTING(init, Fish_init);
END_CTOR
从代码上看,CTOR/END_CTOR 与 ABS_CTOR/END_ABS_CTOR 的使用方式完全相同,的确是,不过,背后,这两对宏的实现方式略有差异,建议有兴趣的读者,认真研究一下LW_OOPC的源码。这里,简单地说明如下:我们希望明确区分抽象类和具体类的概念,抽象类是不可以创建对象的,而具体类则可以。前面,我们声明了Animal是抽象类,Fish类是具体类,那么,我们希望:
Animal* animal = Animal_new(); // 不允许这样写!
Fish* fish = Fish_new(); // 允许这样写!
在讲解CTOR/END_CTOR宏的时候,又出现一个陌生的宏:SUPER_CTOR。它的功能与Java中的super关键字非常类似。
SUPER_CTOR(Animal);
意为:调用Animal类的构造函数。(建议将SUPER_CTOR写在“构造函数”体的开头)
DTOR和END_DTOR用于定义“析构函数”,例如:
// Expr_node的析构函数(DTOR/END_DTOR用于实现析构函数语义)
DTOR(Expr_node)
if (--cthis->use == 0) // 递减引用计数,如果计数为,释放自己
{
cthis->finalize(cthis); // 释放内存之前先清理资源(其他需要释放的对象)
lw_oopc_free(cthis);
}
END_DTOR
这里,特别说明一点,为了模拟C++中的this指针,我们允许用户在ABS_CTOR/END_ABS_CTOR、CTOR/END_CTOR、DTOR/END_DTOR定义块中可以直接使用cthis。
SUPER_PTR用于“向上转型”,将对象指针向上转型成直接父类或者直接接口:
Fish* fish = Fish_new(); // 创建鱼对象
// 初始化鱼对象的昵称为:小鲤鱼,年龄为:1岁
fish->init(fish, "小鲤鱼", 1);
// 将fish指针转型为Animal类型指针,并赋值给animals数组的第一个成员
Animal* animal = SUPER_PTR(fish, Animal);
// 将fish指针转型为IMoveable接口类型指针,并赋值给moveOjbs数组的第一个成员
IMoveable* moveFish = SUPER_PTR(fish, IMoveable);
这里,直接父类很容易理解,直接接口,呵呵,暂且认为我是首创的吧。
我们再来看一下Fish类的声明代码:
CLASS(Fish)
{
EXTENDS(Animal); // 继承Animal抽象类
IMPLEMENTS(IMoveable); // 实现IMoveable接口
void (*init)(Fish* t, const char* name, int age); // 初始化昵称和年龄
};
看:IMPLEMENTS(IMoveable); // 实现IMoveable接口
对Fish类来讲,IMoveable就是它的直接接口。
SUPER_PTR_2和SUPER_PTR_3是SUPER_PTR的高级版本,它们的作用与SUPER_PTR是完全类似的,都是向上转型,只不过,SUPER_PTR_2是向上转两次,SUPER_PTR_3是向上转三次。也就是说,SUPER_PTR_2用于将自身的指针转型为爷爷辈指针,SUPER_PTR_3用于将自身的指针转型为曾祖辈指针。看看SUPER_PTR_2和SUPER_PTR_3的代码:
#define SUPER_PTR_2(cthis, father, grandfather) \
SUPER_PTR(SUPER_PTR(cthis, father), grandfather)
#define SUPER_PTR_3(cthis, father, grandfather, greatgrandfather) \
SUPER_PTR(SUPER_PTR_2(cthis, father, grandfather), greatgrandfather)
看到了吧,SUPER_PTR_2其实是两次SUPER_PTR的叠加。SUPER_PTR_3是三次SUPER_PTR的叠加。由于转型两次或者转型三次,会使得程序过于复杂,所以,建议大家合理组织类的继承关系,尽力避免使用二次转型和三次转型。
SUB_PTR用于“向下转型”,将父类指针向下转型成子类:
/* 鱼的吃行为*/
void Fish_eat(Animal* t)
{
Fish* fish = SUB_PTR(t, Animal, Fish);
…… // 这里可以访问Fish类的成员
printf("鱼吃水草!\n");
}
eat方法是Animal的一个方法,Fish类覆写了该方法,注意,由于该方法的第一个参数类型是:Animal*,在实现Fish类的eat方法Fish_eat时,如果想要访问到Fish类的成员,需要将第一个参数向下转型成Fish*,这就是SUB_PTR所完成的事情。
SUB_PTR_2和SUB_PTR_3是SUB_PTR的高级版本,它们的作用与SUB_PTR是完全类似的,都是向下转型,只不过,SUB_PTR_2是向下转两次,SUB_PTR_3是向下转三次。也就是说,SUB_PTR_2用于将自身的指针转型为孙子辈指针,SUB_PTR_3用于将自身的指针转型为曾孙辈指针。看看SUB_PTR_2和SUB_PTR_3的代码:
#define SUB_PTR_2(selfptr, self, child, grandchild) \
SUB_PTR(SUB_PTR(selfptr, self, child), child, grandchild)
#define SUB_PTR_3(selfptr, self, child, grandchild, greatgrandchild) \
SUB_PTR(SUB_PTR_2(selfptr, self, child, grandchild), grandchild, greatgrandchild)
看到了吧,SUB_PTR_2其实是两次SUB_PTR的叠加。SUB_PTR_3是三次SUB_PTR的叠加。由于转型两次或者转型三次,会使得程序过于复杂,所以,建议大家合理组织类的继承关系,尽力避免使用二次转型和三次转型。
INHERIT_FROM用于访问直接父类的成员,例如:
Dog* dog = Dog_new(); // 创建狗对象
// 初始化狗对象的昵称为:牧羊犬,年龄为:2岁
dog->init(dog, "牧羊犬", 2);
INHERIT_FROM(Animal, dog, age) = 3; // 把牧羊犬的年龄修改为3岁
printf("狗的年龄是:%d岁!\n", INHERIT_FROM(Animal, dog, age)); // 打印狗的年龄
注意,LW_OOPC的上一个版本,我们同时提供了INHERIT_FROM_2和INHERIT_FROM_3这两个宏,INHERIT_FROM_2用于访问爷爷辈的成员,INHERIT_FROM_3用于访问曾祖辈的成员。我们认为应当尽量避免使用INHERIT_FROM_2和INHERIT_FROM_3宏,因为,这会导致类的继承关系中存在严重的数据耦合(自身类可以直接访问爷爷辈,甚至曾祖父辈的成员),这将导致程序难于理解,难于维护。因此,在当前版本中,删除了INHERIT_FROM_2和INHERIT_FROM_3宏,仅仅保留INHERIT_FROM。一般情况下,我们可以通过更加合理的函数封装,让当前类通过祖先类提供的方法间接地访问祖先类的成员。如果确实要在当前类中直接访问爷爷辈甚至曾祖辈的成员,我们可以先通过SUPER_PTR_2和SUPER_PTR_3将当前对象的指针转型为对应的祖先类指针,然后再通过其指针访问其成员。