Despacho de métodos dinámicos en C

8 minutos de lectura

avatar de usuario
Dineshkumar

Sé que suena tonto y sé que C no es un lenguaje orientado a objetos.

Pero, ¿hay alguna forma de que el envío de métodos dinámicos se pueda lograr en C? Pensé en los punteros de función, pero no entiendo la idea completa.

¿Cómo podría implementar esto?

  • Puede hacer esto imitando la forma en que se hace en otros idiomas “bajo el capó”. En c ++, por ejemplo, el envío se realiza buscando un índice en una tabla de punteros de función. Cuando subclasifica algo, agrega sus cargadores al final de la tabla. Puede usar una estructura de porteros de función para representar la tabla y simplemente incluir la estricta de la clase principal al comienzo de la suya para herencia.

    – Voluntad

    12 de julio de 2013 a las 18:26

  • Hagamos de esto una discusión práctica diciendo lo que quiere lograr en su aplicación. Entonces podemos proporcionar ejemplos de código e ideas viables. Su pregunta actual es bastante vaga y se puede responder con Google en 15 minutos.

    – el significado importa

    12 de julio de 2013 a las 18:31


  • ¿Consideraría una función de devolución de llamada como un envío de método dinámico?

    – jxh

    12 de julio de 2013 a las 18:37

  • Parece que usted se beneficiaría de leer acerca de «Programación Orientada a Objetos con ANSI C».

    usuario405725

    12/07/2013 a las 19:45

  • Dos puntos: Objective-C es un superconjunto estricto de C y se implementó como un preprocesador especializado; En segundo lugar, algunas bibliotecas hacen un gran trabajo al usar OOP usando c desnudo con estructuras de punteros de función y miembros de datos… Me viene a la mente la biblioteca serf http.

    – Jugador Grady

    13 de julio de 2013 a las 7:11


avatar de usuario
jxh

Como otros han señalado, ciertamente es posible implementar esto en C. No solo es posible, es un mecanismo bastante común. El ejemplo más utilizado es probablemente la interfaz del descriptor de archivos en UNIX. A read() la llamada a un descriptor de archivo se enviará a una función de lectura específica para el 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 el tipo “base”, no el tipo “derivado”. C no tiene herencia per sepero garantiza que el puntero al primer elemento de un struct es igual al puntero del contenedor struct. Entonces puede usar esto para recuperar el tipo “derivado” haciendo que la instancia de la “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 el struct Stack * a lo que sabe que debe 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 la pila, la operación se enviará a la operación correspondiente en el vtable. Esta vtable es inicializado por la función de creación con las funciones que corresponden a su implementación particular. Entonces:

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

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

stack_push() se llama a los dos s1 y s2. Pero para s1se enviará a stack_array_push()mientras que para s2se enviará a stack_list_push().

  • También, vtable el miembro es generalmente consty apunta a una memoria constante (si MMU/MPU está ahí y el sistema operativo hace lo correcto, por supuesto).

    usuario405725

    27 de septiembre de 2013 a las 1:26

  • Escribiste como: struct StackList *sl = malloc(sizeof(*sl)); Pero creo que debería ser como: struct StackList *sl = malloc(sizeof(*StackList));

    – LORDTEK

    13 oct 2021 a las 19:55


  • @LORDTEK Te aseguro que el mío es correcto. sizeof acepta una expresión e informa el tamaño del tipo de expresión. El tuyo parece un error de sintaxis.

    – jxh

    13 oct 2021 a las 20:56

Si. Se puede lograr fácilmente. Usaría una matriz de punteros de función, luego usaría esos punteros de función para hacer la llamada. Si desea “anular” una función, simplemente configure la ranura correspondiente para que apunte a la nueva función. Así es exactamente como C++ implementa las funciones virtuales.

  • Es un buen comienzo, pero también debe realizar un seguimiento de los tipos de datos para saber qué versión usar.

    – Chris Stratton

    12 de julio de 2013 a las 18:28

  • Puede encontrar una descripción bastante completa de la programación orientada a objetos en C en cs.rit.edu/~ats/books/ooc.pdf

    – cmaster – reincorporar a monica

    12 de julio de 2013 a las 18:37

avatar de usuario
el significado importa

C++ se construyó (inicialmente) sobre C. Los primeros compiladores de C++ en realidad generaban C como paso intermedio. Ergo, sí es posible.

Aquí está cómo C++ hace cosas como esta.

Hay mucha información sólida disponible en línea, más de la que podemos escribir juntos aquí en unos minutos. “Google y encontrarás”.

Dijiste en un comentario arriba:

Bueno, alguien preferiría que si ya tiene algo de código escrito en c, pero agregue alguna funcionalidad. en lugar de escribir desde cero usando OOlanguage.

Para tener una funcionalidad como esta en C, básicamente necesitará volver a implementar las características del lenguaje OO. Lograr que la gente use este nuevo método OO es el factor más importante en contra de la usabilidad. En otras palabras, al crear otro método más para la reutilización, en realidad hará que las cosas sean menos reutilizables.

  • Decir que es posible sin explicar cómo es una respuesta bastante débil.

    – Chris Stratton

    12 de julio de 2013 a las 18:26

  • @ChrisStratton Estoy de acuerdo, lo extenderé.

    – el significado importa

    12 de julio de 2013 a las 18:26

  • Probé en línea y no funcionó. ¡Me muestra solo c ++ y el concepto de Java!

    – Dineshkumar

    12 de julio de 2013 a las 18:34

avatar de usuario
Federico

Estoy un poco sorprendido de que nadie haya agregado glib y/o todo el material de gtk como ejemplo. Así que por favor revisa:
http://www.gtk.org/features.php

Enlace de trabajo a partir del 2021-07-07: https://developer.gnome.org/glib/2.26/

Soy consciente de que es un código bastante repetitivo que debe tener para usar gtk y no es tan “fácil” hacerlo bien la primera vez. Pero si uno lo ha usado es notable. Lo único que debe recordar es usar una especie de “Objeto” como primer parámetro para sus funciones. Pero si revisa la API, verá que se usa en todas partes. En mi humilde opinión, eso es realmente un buen ejemplo sobre los méritos y los problemas que uno puede tener con OOP-

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad