Как отмечали другие, это, безусловно, возможно реализовать на 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()
вызывается как для s1
, так и для s2
. Но для s1
он будет отправлен на stack_array_push()
, а для s2
— на stack_list_push()
.
person
jxh
schedule
12.07.2013