1

我想在 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 传递给每个“方法”的情况下编写一些东西。有任何想法吗?也许一些宏观技巧?

4

3 回答 3

6

如果这是公共接口的一部分,您可以添加访问器函数。一个隐藏的好处是您可以在访问器中进行完整性检查和其他工作。(注意我将“this”指针称为“o”,就像在“object”中一样。为了保持一致性,我更喜欢这种方式。)

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 时,我更喜欢对结构进行 typedef 并给它们命名为 CamelCase 名称。我使用这个约定来说明结构是不透明的,并且只能通过其公共接口访问。它看起来也更好,尽管这是主观的。我也更喜欢让用户为结构本身分配内存,而不是构造函数分配它。这避免了必须处理 malloc 失败,并使程序更有效率。

typedef struct {
    ...
} Statement;

void Statement_Init (Statement *o);
int Statement_Analyse (Statement *o, int pc);
于 2012-04-22T09:04:28.973 回答
3

不幸的是,编写方法以允许传递 aselfthis对象是在 C 中实现此目的的唯一方法。

您可以使用宏技巧来隐藏它的一部分,但此时它不再是真正的 C。

于 2012-04-22T08:59:53.860 回答
2

正如其他答案所说,如果不使用适当的指针调用函数,就无法做到这一点,但是(正如 Williham Totland 建议的那样)您可以使用宏来简化调用(需要具有可变参数宏支持的编译器):

// 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, ),我认为可以解决这个问题,但它很尴尬并且需要更多的代码(__VA_ARGS__众所周知,空很难处理)。

于 2012-04-22T09:42:23.710 回答