【问题标题】:Dynamic method dispatching in CC中的动态方法调度
【发布时间】:2013-07-11 09:18:02
【问题描述】:

我知道这听起来很傻,而且我知道 C 不是面向对象的语言。

但是有什么方法可以在 C 中实现动态方法分派? 我考虑过函数指针,但没有完全理解。

我该如何实现呢?

【问题讨论】:

  • 您可以通过模仿其他语言“在后台”完成的方式来做到这一点。例如,在 C++ 中,调度是通过在函数指针表中查找索引来执行的。当您对某些东西进行子类化时,您会将搬运工添加到表格的末尾。您可以使用函数移植器的结构来表示表,并且只需在您的开头包含父类的严格以进行继承。
  • 让我们通过告诉您希望在应用程序中实现什么来进行实际讨论。然后我们可以提供代码示例和可行的想法。您当前的问题比较模糊,可以在 15 分钟内通过 Google 回答。
  • 你认为回调函数是动态方法调度吗?
  • 听起来你会受益于阅读«Object Oriented Programming with ANSI C»
  • 两点:Objective-C 是 C 的严格超集,被实现为专门的预处理器;其次,一些库使用带有函数指针和数据成员结构的裸 C 语言在 oop 方面做得很好……我想到了 serf http 库。

标签: c oop function-pointers vtable


【解决方案1】:

我有点惊讶没有人添加 glib 和/或整个 gtk 的东西作为例子。所以请检查: http://www.gtk.org/features.php

截至 2021 年 7 月 7 日的工作链接:https://developer.gnome.org/glib/2.26/

我知道使用 gtk 必须有相当多的样板代码,而且第一次就做好并不是那么“容易”。但如果有人使用它,那就太了不起了。您唯一需要记住的是使用一种“对象”作为函数的第一个参数。但是,如果您查看 API,您会发现它无处不在。恕我直言,这确实是一个很好的例子,说明 OOP 可能存在的优点和问题-

【讨论】:

  • 链接未找到!你能用新的编辑它吗?
【解决方案2】:

C++(最初)构建在 C 之上。最初的 C++ 编译器实际上是生成 C 作为中间步骤。因此,是的,这是可能的。

Here's C++ 是如何做到这样的事情的。

网上有大量可靠的信息,比我们在几分钟内一起输入的信息还要多。 “谷歌,你会找到的。”

你在上面的评论中说:

好吧,如果他们已经用 c 编写了一些代码,但要添加一些功能,那么他们会更愿意这样做。而不是使用 OO 语言从头开始编写。

要在 C 中拥有这样的功能,您基本上需要重新实现 OO 语言功能。让人们使用这种新的 OO 方法是影响可用性的最大因素。换句话说,通过创建另一种重用方法,您实际上会降低可重用性。

【讨论】:

  • 说有可能而不解释如何是一个相当薄弱的答案。
  • @ChrisStratton 我同意,我会延长。
  • 我在网上试过了,没有用。只向我展示了 c++ 和 java 的概念!
【解决方案3】:

正如其他人所指出的,在 C 中实现这一点当然是可能的。这不仅是可能的,而且是一种相当普遍的机制。最常用的例子可能是 UNIX 中的文件描述符接口。对文件描述符的read() 调用将分派到特定于提供该文件描述符的设备或服务的读取函数(是文件吗?是套接字吗?是其他类型的设备吗?)。

唯一的技巧是从抽象类型中恢复指向具体类型的指针。对于文件描述符,UNIX 使用包含特定于该描述符的信息的查找表。如果您使用指向对象的指针,则接口用户持有的指针是“基”类型,而不是“派生”类型。 C 没有继承本身,但它确实保证指向struct 的第一个元素的指针等于包含struct 的指针。因此,您可以通过使“基”的实例成为“派生”的第一个成员来使用它来恢复“派生”类型。

这是一个带有堆栈的简单示例:

struct Stack {
    const struct StackInterface * const vtable;
};

struct StackInterface {
    int (*top)(struct Stack *);
    void (*pop)(struct Stack *);
    void (*push)(struct Stack *, int);
    int (*empty)(struct Stack *);
    int (*full)(struct Stack *);
    void (*destroy)(struct Stack *);
};

inline int stack_top (struct Stack *s) { return s->vtable->top(s); }
inline void stack_pop (struct Stack *s) { s->vtable->pop(s); }
inline void stack_push (struct Stack *s, int x) { s->vtable->push(s, x); }
inline int stack_empty (struct Stack *s) { return s->vtable->empty(s); }
inline int stack_full (struct Stack *s) { return s->vtable->full(s); }
inline void stack_destroy (struct Stack *s) { s->vtable->destroy(s); }

现在,如果我想使用固定大小的数组来实现堆栈,我可以这样做:

struct StackArray {
    struct Stack base;
    int idx;
    int array[STACK_ARRAY_MAX];
};
static int stack_array_top (struct Stack *s) { /* ... */ }
static void stack_array_pop (struct Stack *s) { /* ... */ }
static void stack_array_push (struct Stack *s, int x) { /* ... */ }
static int stack_array_empty (struct Stack *s) { /* ... */ }
static int stack_array_full (struct Stack *s) { /* ... */ }
static void stack_array_destroy (struct Stack *s) { /* ... */ }
struct Stack * stack_array_create () {
    static const struct StackInterface vtable = {
        stack_array_top, stack_array_pop, stack_array_push,
        stack_array_empty, stack_array_full, stack_array_destroy
    };
    static struct Stack base = { &vtable };
    struct StackArray *sa = malloc(sizeof(*sa));
    memcpy(&sa->base, &base, sizeof(base));
    sa->idx = 0;
    return &sa->base;
}

如果我想使用列表来实现堆栈:

struct StackList {
    struct Stack base;
    struct StackNode *head;
};
struct StackNode {
    struct StackNode *next;
    int data;
};
static int stack_list_top (struct Stack *s) { /* ... */ }
static void stack_list_pop (struct Stack *s) { /* ... */ }
static void stack_list_push (struct Stack *s, int x) { /* ... */ }
static int stack_list_empty (struct Stack *s) { /* ... */ }
static int stack_list_full (struct Stack *s) { /* ... */ }
static void stack_list_destroy (struct Stack *s) { /* ... */ }
struct Stack * stack_list_create () {
    static const struct StackInterface vtable = {
        stack_list_top, stack_list_pop, stack_list_push,
        stack_list_empty, stack_list_full, stack_list_destroy
    };
    static struct Stack base = { &vtable };
    struct StackList *sl = malloc(sizeof(*sl));
    memcpy(&sl->base, &base, sizeof(base));
    sl->head = 0;
    return &sl->base;
}

堆栈操作的实现将简单地将struct Stack * 转换为它知道的应该是什么。例如:

static int stack_array_empty (struct Stack *s) {
    struct StackArray *sa = (void *)s;
    return sa->idx == 0;
}

static int stack_list_empty (struct Stack *s) {
    struct StackList *sl = (void *)s;
    return sl->head == 0;
}

当堆栈的用户调用堆栈实例上的堆栈操作时,该操作将分派到vtable 中的相应操作。这个vtable 由创建函数使用与其特定实现相对应的函数进行初始化。所以:

Stack *s1 = stack_array_create();
Stack *s2 = stack_list_create();

stack_push(s1, 1);
stack_push(s2, 1);

stack_push()s1s2 调用。但是,对于s1,它将分派到stack_array_push(),而对于s2,它将分派到stack_list_push()

【讨论】:

  • 另外,vtable 成员通常是const,并指向一个常量内存(当然,如果 MMU/MPU 存在并且操作系统做正确的事情)。
  • 你写成: struct StackList *sl = malloc(sizeof(*sl));但我认为它应该是:struct StackList *sl = malloc(sizeof(*StackList));
  • @LORDTEK 我向你保证我的是正确的。 sizeof 接受表达式并报告表达式类型的大小。你的看起来像是语法错误。
【解决方案4】:

是的。它可以很容易地实现。您将使用函数指针数组,然后使用这些函数指针进行调用。如果要“覆盖”一个函数,只需将相应的槽设置为指向新函数即可。这正是 C++ 实现虚函数的方式。

【讨论】:

  • 这是一个好的开始 - 但您还需要跟踪数据的类型,以便知道要使用哪个版本。
  • 可以在cs.rit.edu/~ats/books/ooc.pdf找到关于 C 中 OO 编程的非常全面的描述
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多