我想在C语言中使用多态进行一些面向对象的样式编程,其中我的接口类包含指向函数表的指针。例如:
/* Implement polymorphism in C, Linux kernel-style */
struct statement {
const struct statement_ops *ops;
struct list_head list; /* when on master input list */
void *private; /* pointer to type-specific data */
};
struct statement_ops {
int (*analyse)(void *private, int pc);
int (*get_binary_size)(void *private);
};
void user(void)
{
struct statement *s = make_a_statement();
if (s->ops->analyse(s->private, foo))
blah blah;
}
我希望能够在没有明确地将s-> private传递给每个“方法”的情况下编写内容。有任何想法吗?一些宏观技巧可能吗?
答案 0 :(得分:6)
如果这是公共界面的一部分,您可以添加访问者功能。隐藏的好处是您可以在访问者中进行健全性检查和其他工作。 (注意我打电话给"这"指针" o",如"对象"。我更喜欢这种方式以保持一致性。)
int statement_analyse (struct statement *o, int pc)
{
assert(pc >= 0);
int ret = o->ops->analyse(o->private, pc);
assert(ret >= 0);
return ret;
}
您现在可以在没有明确传递" private"。
的情况下调用此方法void user(void)
{
struct statement *s = make_a_statement();
if (statement_analyse(s, foo))
blah blah;
}
虽然看起来这似乎没有任何好处,因为你仍然必须实现访问器,假设你需要一个定义良好且健壮的接口,访问器函数是放置断言和接口文档的唯一理智的地方。实际上,如果你编写好的断言,断言本身有助于记录接口。一旦你在访问者中添加了健全性检查,你就不必在他们调用的实际方法中添加它们。
当然,这种方法只有在通过函数指针调用的函数是用户提供的东西时才有意义,或者以某种其他方式可以是不同的东西。如果单个analyse()
方法总是做同样的事情,那么您只需实现一个statement_analyse()
直接执行它需要做的事情。
小注意:在进行OOP时,我更喜欢键入结构并给它们CamelCase名称。我使用这个约定来告诉结构是不透明的,只能通过它的公共接口访问。虽然这是主观的,但它看起来也更好。我也更喜欢让用户为结构本身分配内存,而不是构造函数malloc&#39。这避免了必须处理malloc故障,并使程序更有效。
typedef struct {
...
} Statement;
void Statement_Init (Statement *o);
int Statement_Analyse (Statement *o, int pc);
答案 1 :(得分:3)
不幸的是,编写允许传递self
或this
对象的方法是在C中实现此目的的唯一方法。
你可以使用宏技巧隐藏它的一部分,但那时它不再是C了。
答案 2 :(得分:2)
正如其他答案所说,没有用适当的指针调用函数就没有办法做到这一点,但是(正如Williham Totland建议的那样)你可以使用宏来简化调用(要求编译器使用variadic macro支持):
// macro_call.c
#define C_ARGS(stmnt, func, ...) (stmnt)->ops->func((stmnt)->private, ...)
#define C_NOARGS(stmnt, func) (stmnt)->ops->func((stmnt)->private)
C_ARGS(s, analyse, 1);
C_ARGS(s, lots_of_args, 1, 2, 3, 4);
C_NOARGS(s, no_args);
(C
用于“通话”。)
对此进行预处理(通过gcc -E macro_call.c
)会产生:
(s)->ops->analyse((s)->private, 1);
(s)->ops->lots_of_args((s)->private, 1, 2, 3, 4);
(s)->ops->no_args((s)->private);
这类似于访问者功能版本:宏版本在某些方面稍微更灵活,但它也不太安全,可能导致细微的错误和错误。
有两个宏,因为没有额外的参数传递给C_ARGS
会产生s->ops->func(s->private, )
,我认为有可能解决这个问题,但它很尴尬,需要更多的代码(空{{ 1}}众所周知很难处理。)