我有一个必须在C中的项目(只是为了避免使用C ++参数)。
项目依赖于实现多态的虚拟表和指针。
但是我坚持从多级继承实现超级构造函数。
示例结构是:
Base Object
/\ /\
Constructed Object Simple Object
/\
SpecificConstructed
所有对象都有名称和类。 例如,构造的对象可以具有子对象列表。 简单对象可能只添加一个值。
基础对象刚定义为:
struct _object {
struct _class *class;
char *name;
}
Class是虚拟表的位置:
struct _class {
struct _class *super;
char *name;
size_t size;
void* (*init)(void *_this, char *name);
...
}
构造的对象是:
struct _constructed_object {
struct _object base;
void* components; //a list of sub objects for example
}
示例简单对象是:
struct _simple_object {
struct _object base;
unsigned char value; //a simple value for this specific type
}
所以每个对象都有一个类,类可以有超级,特别是对于SpecificConstructed - >构造
我的定义:
struct _class base = {0, "Base", sizeof(struct _object), base_init};
struct _class constructed = {&base, "Constructed", sizeof(struct _constructed_object}, constructed_init};
struct _class specific = {&constructed, "SpecificConstructed", sizeof(struct _constructed_object), specific_init};
struct _class simple = {&base, "SimpleOBject", sizeof(struct _simple_object}, simple_init};
这个定义允许我使用函数创建指定类的对象:
new(struct _class *a_class) {
...
struct _object *o = calloc(1, a_class->size);
o->class = a_class;
o = a_class->init(o);
return o;
}
我的想法是:
new(SpecificConstructed)
New会创建适当的空间(sizeof(struct _constructed_object)),它会调用" specific_init&#34 ;,这反过来会调用" construct_init" (它的超级),最终会调用" base_init" (它的超级)。但是,流程是specific_init,construct_init,specific_init,construct_init ......
我调用超级初始值设定项的函数:
void* super_init(void* _this, char *name){
struct _object *o = (struct _object*)_this;
const struct _class *c = o->class;
const struct _class *s = c->super;
return (s && s->init) ? s->init(_this, name) : _this;
}
简单(to - super)基本方法调用有效,因为我可以调用超级init。
但是对于特定的构造,调用super会将我带到构造的对象,这是正确的步骤,但是然后它不是构造发送到base_init,而是将我发送回specific_init调用。发生这种情况是因为我传递了相同的_这个对象,该对象以特定的类开头,我明白了,但不确定如何修复它以及它是否真的可以修复?
我已经阅读了面向对象的C书,但是它涉及一级继承Circle-> Point,而Metaclasses章只是飞过我的脑海。我还查看了Objective-C运行时,看看它是如何处理它的,但它也有元类,我现在无法理解。
答案 0 :(得分:1)
这是非常棒的东西,我在九十年代早期在C中做了一些这样的东西,然后再转向C ++。
不幸的是,尽管你的问题很长,但它仍然有点模糊,因为它没有向我们展示某些东西,比如什么是"构造的对象",(为什么是你这样称呼它,"构造对象"之间的区别是什么?和"简单的对象",以及"简单>基本方法调用"。另外,为什么size
?另外,我认为有必要使用一些示例代码来显示构造函数调用的实际问题。
我现在可以告诉你的关于这个设计的一件事是,在虚拟方法表中存储指向构造函数的指针时,我觉得很奇怪。在我所知的所有面向对象语言中,(C ++,Java,C#)构造函数从不虚拟;他们看起来更像是"静态"方法,在C语言中只是简单的逐个链接方法。这样可以正常工作,因为每个类都有内置的,绝对肯定的,不可更改的基类知识。
无论如何,链式构造函数调用应该是这样的:
void basemost_init( struct basemost* this, char* name )
{
this->class = &basemost_class;
this->name = name;
...
}
void intermediate_init( struct intermediate* this, char* name )
{
basemost_init( &this->base, name );
this->class = &intermediate_class;
...
}
void descendant_init( struct descendant* this, char* name )
{
intermediate_init( &this->base, name );
this->class = &descendant_class;
...
}
修改(经过一些澄清)
如果你希望它在分配结束时看起来很酷,或许尝试这样的事情:(我不确定我记得我的C语法有多好,所以请原谅任何轻微的不准确之处。)
struct descendant* new_descendant( char* name )
{
struct descendant* this = malloc( sizeof struct descendant );
descendant_init( this, name );
return this;
}
这样,您就不再需要size
了。另外,请注意,您可以根据需要传递任意数量的构造函数参数,而不限于固定的预定数量的参数(我认为这非常重要),而且不必使用可变参数构造函数。
如果您承诺使用一致的命名,您也可以使用#define
宏为所有类实现相同的功能,以便每个构造函数的名称始终可以计算为structname##_init
,但我不确定如何在this
指针之后通过C中的宏传递任意构造函数参数。无论如何,我更愿意避免使用宏,除非它们是绝对必要的,在这种情况下他们不是。
答案 1 :(得分:1)
super_init
无法正常工作,需要 class 来调用super,否则(正如您所发现的那样)直接超类构造函数最终会反复调用自身。由于每个类都知道它的父类,因此它可以直接调用超类的init。例如,simple.init
会调用specific.init
,然后调用constructed.init
,依此类推。
如果你坚持使用一个函数为你做这个,你必须给它一个类,这样它就可以(平凡地)调用超类的构造函数。 super
in Python 2就是这种设计的一个例子。 Python 3引入了一个更简单易用的super
,但它需要编译器的支持才能找出传递给底层函数的正确类。