Método dinámico de despacho en C
oop function-pointers (4)
C ++ (inicialmente) se construyó sobre C. El primer compilador de C ++ en realidad estaba generando C como paso intermedio. Ergo, sí, es posible.
Así Here''s como C ++ hace cosas como esta.
Hay mucha información sólida disponible en línea, más de la que podemos escribir aquí en unos minutos. "Google y usted encontrará".
Usted dijo en un comentario anterior:
Bueno, alguien preferiría que si ya tuvieran algo de código escrito en c para agregar alguna funcionalidad. En lugar de escribir desde cero usando OOlanguage.
Para tener una funcionalidad como esta en C, básicamente deberá volver a implementar las características del lenguaje OO. Lograr que las personas utilicen este nuevo método OO es el factor más importante contra la usabilidad. En otras palabras, al crear otro método de reutilización, en realidad hará que las cosas sean menos reutilizables.
Sé que suena tonto y sé que C no es un lenguaje orientado a objetos.
Pero, ¿hay alguna manera de que el envío de métodos dinámicos se pueda lograr en C? Pensé en punteros de función, pero no entiendo la idea completa.
¿Cómo podría implementar esto?
Estoy un poco sorprendido de que nadie haya agregado glib y / o todo el material de gtk como ejemplo. Así que por favor revise: http://www.gtk.org/features.php
Soy consciente de que es bastante necesario tener un código para usar gtk y no es tan "fácil" acertar la primera vez. Pero si uno lo ha utilizado es notable. Lo único que debes recordar es utilizar un tipo de "Objeto" como primer parámetro para tus funciones. Pero si miras a través de la API, verás que se usa en todas partes. En mi humilde opinión es un buen ejemplo de los méritos y problemas que uno puede tener con la POO
Sí. Se puede lograr fácilmente. Usaría una serie de punteros de función, luego usaría esos punteros de función para realizar la llamada. Si desea "anular" una función, simplemente configure la ranura correspondiente para que apunte a la nueva función. Así es exactamente cómo C ++ implementa funciones virtuales.
Como han señalado otros, es ciertamente posible implementar esto en C. No solo es posible, sino que es un mecanismo bastante común. El ejemplo más utilizado es probablemente la interfaz del descriptor de archivos en UNIX. Una llamada read()
en un descriptor de archivo se enviará a una función de lectura específica del dispositivo o servicio que proporcionó ese descriptor de archivo (¿era un archivo? ¿Era un socket? ¿Era algún otro tipo de dispositivo?).
El único truco es recuperar el puntero al tipo concreto del tipo abstracto. Para los descriptores de archivos, UNIX utiliza una tabla de búsqueda que contiene información específica de ese descriptor. Si está utilizando un puntero a un objeto, el puntero que tiene el usuario de la interfaz es del tipo "base", no del tipo "derivado". C no tiene herencia per se , pero garantiza que el puntero al primer elemento de una struct
es igual al puntero de la struct
contenedora. Así que puedes usar esto para recuperar el tipo "derivado" haciendo que la instancia de "base" sea el primer miembro del "derivado".
Aquí hay un ejemplo simple con una pila:
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); }
Ahora, si quisiera implementar una pila usando una matriz de tamaño fijo, podría hacer algo como esto:
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;
}
Y si quisiera implementar una pila usando una lista en su lugar:
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;
}
Las implementaciones de las operaciones de pila simplemente convertirían la struct Stack *
a lo que sabe que debería ser. Por ejemplo:
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;
}
Cuando un usuario de una pila invoca una operación de pila en la instancia de pila, la operación se enviará a la operación correspondiente en el vtable
. Este vtable
se inicializa mediante la función de creación con las funciones que corresponden a su implementación particular. Asi que:
Stack *s1 = stack_array_create();
Stack *s2 = stack_list_create();
stack_push(s1, 1);
stack_push(s2, 1);
Se llama a stack_push()
tanto en s1
como en s2
. Pero, para s1
, se enviará a stack_array_push()
, mientras que para s2
, se enviará a stack_list_push()
.