c - C中的动态方法调度

标签 c oop function-pointers vtable

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

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

我该如何实现?

最佳答案

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

唯一的技巧是从抽象类型中恢复指向具体类型的指针。对于文件描述符,UNIX 使用包含特定于该描述符的信息的查找表。如果您使用的是指向对象的指针,则接口(interface)用户持有的指针是“基”类型,而不是“派生”类型。 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;
}

当堆栈的用户在堆栈实例上调用堆栈操作时,该操作将分派(dispatch)到 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().

关于c - C中的动态方法调度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17621544/

相关文章:

c - 警告 : implicit declaration of function

c# - 创建一个包罗万象的 AppToolbox 类——这是一种不好的做法吗?

c++ - 比较 std::functions 是否相等?

c - ELOG 消息去哪儿了?

c - fork() 子进程实际上在什么时候开始?

c - 尝试在 c 中分配内存函数?

java - Scala 选择在签名冲突的情况下实现哪个特征?

php - 如何通过 Controller 调用模型中的方法? Zend 框架

javascript - 传递 self (函数)

c++ - 根据变量的值执行函数 - C++