Orientación a objetos en C

Resuelto asked hace 15 años • 23 respuestas

¿Cuál sería un conjunto de ingeniosos trucos de preprocesador (compatibles con ANSI C89/ISO C90) que permitan algún tipo de orientación a objetos fea (pero utilizable) en C?

Estoy familiarizado con algunos lenguajes orientados a objetos diferentes, así que no responda con respuestas como "¡Aprenda C++!". He leído " Programación orientada a objetos con ANSI C " (cuidado: formato PDF ) y varias otras soluciones interesantes, ¡pero lo que más me interesa es la tuya :-)!


Véase también ¿Puedes escribir código orientado a objetos en C?

 avatar Jan 06 '09 11:01
Aceptado

Yo desaconsejaría el uso de preprocesador (ab) para intentar hacer que la sintaxis de C se parezca más a la de otro lenguaje más orientado a objetos. En el nivel más básico, simplemente usa estructuras simples como objetos y las pasa mediante punteros:

struct monkey
{
    float age;
    bool is_male;
    int happiness;
};

void monkey_dance(struct monkey *monkey)
{
    /* do a little dance */
}

Para conseguir cosas como la herencia y el polimorfismo, hay que trabajar un poco más. Puede hacer herencia manual haciendo que el primer miembro de una estructura sea una instancia de la superclase, y luego puede generar punteros a clases base y derivadas libremente:

struct base
{
    /* base class members */
};

struct derived
{
    struct base super;
    /* derived class members */
};

struct derived d;
struct base *base_ptr = (struct base *)&d;  // upcast
struct derived *derived_ptr = (struct derived *)base_ptr;  // downcast

Para obtener polimorfismo (es decir, funciones virtuales), se utilizan punteros de función y, opcionalmente, tablas de punteros de función, también conocidas como tablas virtuales o vtables:

struct base;
struct base_vtable
{
    void (*dance)(struct base *);
    void (*jump)(struct base *, int how_high);
};

struct base
{
    struct base_vtable *vtable;
    /* base members */
};

void base_dance(struct base *b)
{
    b->vtable->dance(b);
}

void base_jump(struct base *b, int how_high)
{
    b->vtable->jump(b, how_high);
}

struct derived1
{
    struct base super;
    /* derived1 members */
};

void derived1_dance(struct derived1 *d)
{
    /* implementation of derived1's dance function */
}

void derived1_jump(struct derived1 *d, int how_high)
{
    /* implementation of derived 1's jump function */
}

/* global vtable for derived1 */
struct base_vtable derived1_vtable =
{
    &derived1_dance, /* you might get a warning here about incompatible pointer types */
    &derived1_jump   /* you can ignore it, or perform a cast to get rid of it */
};

void derived1_init(struct derived1 *d)
{
    d->super.vtable = &derived1_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

struct derived2
{
    struct base super;
    /* derived2 members */
};

void derived2_dance(struct derived2 *d)
{
    /* implementation of derived2's dance function */
}

void derived2_jump(struct derived2 *d, int how_high)
{
    /* implementation of derived2's jump function */
}

struct base_vtable derived2_vtable =
{
   &derived2_dance,
   &derived2_jump
};

void derived2_init(struct derived2 *d)
{
    d->super.vtable = &derived2_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

int main(void)
{
    /* OK!  We're done with our declarations, now we can finally do some
       polymorphism in C */
    struct derived1 d1;
    derived1_init(&d1);

    struct derived2 d2;
    derived2_init(&d2);

    struct base *b1_ptr = (struct base *)&d1;
    struct base *b2_ptr = (struct base *)&d2;

    base_dance(b1_ptr);  /* calls derived1_dance */
    base_dance(b2_ptr);  /* calls derived2_dance */

    base_jump(b1_ptr, 42);  /* calls derived1_jump */
    base_jump(b2_ptr, 42);  /* calls derived2_jump */

    return 0;
}

Y así es como se hace el polimorfismo en C. No es bonito, pero cumple su función. Hay algunos problemas complicados relacionados con la conversión de punteros entre las clases base y derivadas, que son seguras siempre que la clase base sea el primer miembro de la clase derivada. La herencia múltiple es mucho más difícil; en ese caso, para diferenciar entre clases base distintas de la primera, debe ajustar manualmente los punteros en función de las compensaciones adecuadas, lo cual es realmente complicado y propenso a errores.

¡Otra cosa (complicada) que puedes hacer es cambiar el tipo dinámico de un objeto en tiempo de ejecución! Simplemente reasigne un nuevo puntero vtable. Incluso puedes cambiar selectivamente algunas de las funciones virtuales y mantener otras, creando nuevos tipos híbridos. Solo tenga cuidado de crear una nueva vtable en lugar de modificar la vtable global; de lo contrario, afectará accidentalmente a todos los objetos de un tipo determinado.

Adam Rosenfield avatar Jan 06 '2009 05:01 Adam Rosenfield

Una vez trabajé con una biblioteca C que estaba implementada de una manera que me pareció bastante elegante. Habían escrito, en C, una manera de definir objetos y luego heredarlos para que fueran tan extensibles como un objeto C++. La idea básica era esta:

  • Cada objeto tenía su propio archivo.
  • Las funciones y variables públicas se definen en el archivo .h para un objeto
  • Las variables y funciones privadas solo se ubicaron en el archivo .c
  • Para "heredar", se crea una nueva estructura en la que el primer miembro de la estructura es el objeto a heredar.

Heredar es difícil de describir, pero básicamente fue esto:

struct vehicle {
   int power;
   int weight;
}

Luego en otro archivo:

struct van {
   struct vehicle base;
   int cubic_size;
}

Entonces podrías crear una camioneta en la memoria y usarla mediante un código que solo conocía los vehículos:

struct van my_van;
struct vehicle *something = &my_van;
vehicle_function( something );

Funcionó maravillosamente y los archivos .h definieron exactamente lo que debería poder hacer con cada objeto.

Kieveli avatar Jan 06 '2009 04:01 Kieveli

C Object System (COS) suena prometedor (todavía está en versión alfa). Intenta mantener al mínimo los conceptos disponibles en aras de la simplicidad y la flexibilidad: programación uniforme orientada a objetos que incluye clases abiertas, metaclases, metaclases de propiedades, genéricos, multimétodos, delegación, propiedad, excepciones, contratos y cierres. Hay un borrador (PDF) que lo describe.

La excepción en C es una implementación C89 de TRY-CATCH-FINALLY que se encuentra en otros lenguajes OO. Viene con un conjunto de pruebas y algunos ejemplos.

Ambos de Laurent Deniau, que está trabajando mucho en programación orientada a objetos en C.

philant avatar Jan 06 '2009 07:01 philant

El escritorio GNOME para Linux está escrito en C orientado a objetos y tiene un modelo de objetos llamado " GObject " que admite propiedades, herencia, polimorfismo, así como algunas otras ventajas como referencias, manejo de eventos (llamados "señales"), tiempo de ejecución. mecanografía, datos privados, etc.

Incluye trucos de preprocesador para hacer cosas como encasillar en la jerarquía de clases, etc. Aquí hay una clase de ejemplo que escribí para GNOME (cosas como gchar son typedefs):

Fuente de clase

Encabezado de clase

Dentro de la estructura GObject hay un entero GType que se utiliza como número mágico para el sistema de escritura dinámica de GLib (puede convertir toda la estructura a un "GType" para encontrar su tipo).

James Cape avatar Jan 06 '2009 05:01 James Cape

Ligeramente fuera de tema, pero el compilador de C++ original, Cfront , compiló C++ en C y luego en ensamblador.

Conservado aquí .

zebrabox avatar Aug 05 '2009 09:08 zebrabox