Диспетчеризация динамических методов в C

Я знаю, что это звучит глупо, и я знаю, что C не является объектно-ориентированным языком.

Но можно ли как-нибудь реализовать динамическую диспетчеризацию методов в C? Я думал об указателях на функции, но не понял всей идеи.

Как я мог реализовать это?


person Dineshkumar    schedule 12.07.2013    source источник
comment
Вы можете сделать это, подражая тому, как это делается в других языках под капотом. Например, в С++ диспетчеризация выполняется путем поиска индекса в таблице указателей функций. Когда вы подклассифицируете что-то, вы добавляете своих носильщиков в конец таблицы. Вы можете использовать структуру переносчиков функций для представления таблицы и просто включить родительский класс strict в начало вашего для наследования.   -  person Will    schedule 12.07.2013
comment
Давайте сделаем это практическим обсуждением, рассказав, чего вы хотите достичь в своем приложении. Затем мы можем предоставить примеры кода и рабочие идеи. Ваш текущий вопрос довольно расплывчатый, и на него можно ответить через Google за 15 минут.   -  person meaning-matters    schedule 12.07.2013
comment
хорошо, кто-то предпочел бы, чтобы у них был какой-то код, уже написанный на c, но для добавления некоторой функциональности. вместо того, чтобы писать с нуля, используя OOlanguage.   -  person Dineshkumar    schedule 12.07.2013
comment
Считаете ли вы функцию обратного вызова отправкой динамического метода?   -  person jxh    schedule 12.07.2013
comment
Похоже, вам было бы полезно прочитать об «Объект Ориентированное программирование с использованием ANSI C».   -  person    schedule 12.07.2013
comment
объектная ориентация — это всего лишь парадигма.   -  person Ryan Haining    schedule 13.07.2013
comment
Два момента: Objective-C является строгим надмножеством C и был реализован как специализированный препроцессор; во-вторых, некоторые библиотеки отлично справляются с oop, используя голый c со структурами указателей на функции и членов данных ... на ум приходит библиотека serf http.   -  person Grady Player    schedule 13.07.2013
comment
Вам следует прочитать документацию для GObject.   -  person ceving    schedule 07.07.2021


Ответы (4)


Как отмечали другие, это, безусловно, возможно реализовать на 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
comment
Кроме того, член vtable обычно равен const и указывает на постоянную память (если, конечно, MMU/MPU есть и ОС делает все правильно). - person ; 27.09.2013

C++ был (изначально) построен поверх C. Первые компиляторы C++ фактически генерировали C в качестве промежуточного шага. Следовательно, да, это возможно.

Вот как C++ делает подобные вещи.

В Интернете доступно много надежной информации, больше, чем мы можем набрать здесь вместе за несколько минут. Гуглите и найдете.

Вы сказали в комментарии выше:

Ну, кто-то предпочел бы, чтобы у них был код, уже написанный на c, но с добавлением некоторой функциональности. вместо того, чтобы писать с нуля, используя OOlanguage.

Чтобы иметь подобную функциональность в C, вам в основном нужно заново реализовать функции языка OO. Заставить людей использовать этот новый объектно-ориентированный метод — самый большой фактор, препятствующий удобству использования. Другими словами, создав еще один метод для повторного использования, вы на самом деле сделаете вещи менее пригодными для повторного использования.

person meaning-matters    schedule 12.07.2013
comment
Сказать, что это возможно, не объясняя, как, это довольно слабый ответ. - person Chris Stratton; 12.07.2013
comment
@ChrisStratton Я согласен, я продлю. - person meaning-matters; 12.07.2013
comment
я пробовал онлайн и не работал. показывает мне только концепцию С++ и Java! - person Dineshkumar; 12.07.2013

да. Это может быть легко достигнуто. Вы должны использовать массив указателей функций, а затем использовать эти указатели функций для выполнения вызова. Если вы хотите «переопределить» функцию, вы просто устанавливаете соответствующий слот так, чтобы он указывал на новую функцию. Именно так C++ реализует виртуальные функции.

person user2555312    schedule 12.07.2013
comment
Это хорошее начало, но вам также необходимо отслеживать типы ваших данных, чтобы знать, какую версию использовать. - person Chris Stratton; 12.07.2013
comment
Достаточно подробное описание объектно-ориентированного программирования на C можно найти по адресу cs.rit. .edu/~ats/books/ooc.pdf - person cmaster - reinstate monica; 12.07.2013

Я немного удивлен, что никто не добавил glib и/или весь gtk в качестве примера. Пожалуйста, проверьте: http://www.gtk.org/features.php

Рабочая ссылка от 07 июля 2021 г.: https://developer.gnome.org/glib/2.26/

Я знаю, что это довольно шаблонный код, который вам нужно иметь для использования gtk, и это не так просто сделать правильно с первого раза. Но если кто-то использовал его, это замечательно. Единственное, что вы должны помнить, это использовать тип объекта в качестве первого параметра для ваших функций. Но если вы просмотрите API, вы увидите, что он используется повсеместно. ИМХО, это действительно хороший пример достоинств и проблем, которые могут возникнуть с ООП-

person Friedrich    schedule 21.10.2013
comment
Ссылка не найдена! Можете ли вы отредактировать его с помощью нового? - person Vishwajith.K; 06.07.2021