switzerland supermarket programming program oriented language how himmelblau coop biografia arquitecto c oop

supermarket - how to program object oriented in c



OOP e interfaces en C (3)

Directamente entiendo que ANSI C no es un lenguaje de programación orientado a objetos. Quiero aprender cómo aplicar una técnica oo en particular usando c.

Por ejemplo, quiero crear varias clases de efectos de audio que tienen los mismos nombres de funciones pero diferentes implementaciones de esas funciones.

Si estuviera haciendo esto en un lenguaje de nivel superior, primero escribiría una interfaz y luego la implementaría.

AudioEffectInterface -(float) processEffect DelayClass -(float) processEffect { // do delay code return result } FlangerClass -(float) processEffect { // do flanger code return result } -(void) main { effect= new DelayEffect() effect.process() effect = new FlangerEffect() effect.process() }

¿Cómo puedo lograr tal flexibilidad usando C?


¿Puede usted comprometerse con lo siguiente:

#include <stdio.h> struct effect_ops { float (*processEffect)(void *effect); /* + other operations.. */ }; struct DelayClass { unsigned delay; struct effect_ops *ops; }; struct FlangerClass { unsigned period; struct effect_ops *ops; }; /* The actual effect functions are here * Pointers to the actual structure may be needed for effect-specific parameterization, etc. */ float flangerEffect(void *flanger) { struct FlangerClass *this = flanger; /* mix signal delayed by this->period with original */ return 0.0f; } float delayEffect(void *delay) { struct DelayClass *this = delay; /* delay signal by this->delay */ return 0.0f; } /* Instantiate and assign a "default" operation, if you want to */ static struct effect_ops flanger_operations = { .processEffect = flangerEffect, }; static struct effect_ops delay_operations = { .processEffect = delayEffect, }; int main() { struct DelayClass delay = {.delay = 10, .ops = &delay_operations}; struct FlangerClass flanger = {.period = 1, .ops = &flanger_operations}; /* ...then for your signal */ flanger.ops->processEffect(&flanger); delay.ops->processEffect(&delay); return 0; }


Hay tres formas distintas de lograr el polimorfismo en C:

  1. Codificarlo
    En las funciones de clase base, simplemente switch un ID de tipo de clase para llamar a las versiones especializadas. Un ejemplo de código incompleto:

    typedef enum classType { CLASS_A, CLASS_B } classType; typedef struct base { classType type; } base; typedef struct A { base super; ... } A; typedef struct B { base super; ... } B; void A_construct(A* me) { base_construct(&me->super); super->type = CLASS_A; } int base_foo(base* me) { switch(me->type) { case CLASS_A: return A_foo(me); case CLASS_B: return B_foo(me); default: assert(0), abort(); } }

    Por supuesto, esto es tedioso para clases grandes.

  2. Almacenar punteros de función en el objeto
    Puede evitar las instrucciones de cambio utilizando un puntero a función para cada función miembro. De nuevo, este es un código incompleto:

    typedef struct base { int (*foo)(base* me); } base; //class definitions for A and B as above int A_foo(base* me); void A_construct(A* me) { base_construct(&me->super); me->super.foo = A_foo; }

    Ahora, el código de llamada puede hacer

    base* anObject = ...; (*anObject->foo)(anObject);

    Alternativamente, puede usar una macro de preprocesador a lo largo de las líneas de:

    #define base_foo(me) (*me->foo)(me)

    Tenga en cuenta que esto evalúa la expresión me dos veces, por lo que realmente es una mala idea. Esto puede ser arreglado, pero eso está más allá del alcance de esta respuesta.

  3. Usa un vtable
    Como todos los objetos de una clase comparten el mismo conjunto de funciones miembro, todos pueden usar los mismos punteros de función. Esto es muy parecido a lo que C ++ hace bajo el capó:

    typedef struct base_vtable { int (*foo)(base* me); ... } base_vtable; typedef struct base { base_vtable* vtable; ... } base; typedef struct A_vtable { base_vtable super; ... } A_vtable; //within A.c int A_foo(base* super); static A_vtable gVtable = { .foo = A_foo, ... }; void A_construct(A* me) { base_construct(&me->super); me->super.vtable = &gVtable; };

    Nuevamente, esto le permite al código de usuario hacer el envío (con un direccionamiento indirecto adicional):

    base* anObject = ...; (*anObject->vtable->foo)(anObject);

El método que debe utilizar depende de la tarea en cuestión. El enfoque basado en el switch es fácil de preparar para dos o tres clases pequeñas, pero es difícil de manejar para clases y jerarquías grandes. El segundo enfoque se escala mucho mejor, pero tiene una gran sobrecarga de espacio debido a los punteros de función duplicados. El enfoque de vtable requiere bastante estructura adicional e introduce aún más direccionamiento indirecto (lo que hace que el código sea más difícil de leer), pero es sin duda el camino a seguir para jerarquías de clases complejas.


Implementas interfaces utilizando estructuras de punteros de función. Luego puede tener la estructura de interfaz incrustada en su estructura de objeto de datos y pasar el puntero de la interfaz como primer parámetro de cada función de miembro de la interfaz. En esa función, a continuación, obtiene el puntero a su clase contenedora (que es específica de su implementación) utilizando la macro container_of (). Busque "container_of linux kernel" para una implementación. Es una macro muy útil.