2011-06-10 18 views
21

Directo del murciélago Entiendo que ANSI C no es un lenguaje de programación orientado a objetos. Quiero aprender a aplicar una técnica de oo particular usando c.OOP e interfaces en C

Por ejemplo, deseo crear varias clases de efectos de audio que tengan todos 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 lo 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 esa flexibilidad usando C?

+0

Ver http://stackoverflow.com/questions/351733/can-you-write-object-oriented-code-in-c/351745#351745 y http://stackoverflow.com/questions/4103704/experiment- object-oriented-c/4103725 # 4103725 para ejemplos. – paxdiablo

Respuesta

10

¿Se puede comprometer 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; 
} 
+1

esto es interesante. No estoy familiarizado con esta sintaxis "struct DelayClass delay = {.delay = 10, .ops = & operations}; " Podría explicar la sintaxis de punto aquí. – dubbeat

+3

C99 le permite usar los denominados 'inicializadores designados' para inicializar tipos agregados (matrices, uniones, estructuras). La sintaxis es '.member = expression'. –

2

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

17

Hay tres maneras distintas se puede lograr polimorfismo en C:

  1. Código cabo
    En las funciones de la clase base, simplemente switch en una identificación de tipo de clase para llamar a las versiones especializadas. Un ejemplo de código incompleta:

    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 hacer por grandes clases.

  2. tienda punteros de función en el objeto
    Puede evitar las sentencias switch mediante el uso de un puntero de función para cada función miembro. Una vez más, se trata de 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, código de llamada sólo puede hacer

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

    Alternativamente, puede utilizar una macro preprocesador 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 esta es realmente una mala idea. Esto puede ser arreglado, pero eso está más allá del alcance de esta respuesta.

  3. Utilice un vtable
    Dado que todos los objetos de una clase comparten el mismo conjunto de funciones miembro, todos ellos pueden usar los mismos punteros de función.Esto es muy cercano a lo que hace C++ 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; 
    }; 
    

    Una vez más, esto permite que el código de usuario para hacer el envío (con una indirecta adicional):

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

Cuál método debe usar depende de la tarea en cuestión. El enfoque basado en switch es fácil de mejorar para dos o tres clases pequeñas, pero es difícil de manejar para grandes clases y jerarquías. El segundo enfoque escala mucho mejor, pero tiene una gran cantidad de espacio por encima debido a los punteros de función duplicados. El enfoque 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 sin duda es el camino a seguir para las jerarquías de clases complejas.

+0

Excelente respuesta. Esto cubre todas las bases y deja los detalles de implementación a criterio del programador. +1 – slebetman

+0

¿Cómo se puede establecer base_vtable.foo = A_foo? Presumiblemente, la firma de A_foo tomaría una instancia de A como parámetro. ¿No necesitarías al menos lanzar de alguna manera? – weberc2

+1

@ weberc2 Sí, necesitas un elenco. Eso normalmente sería lo primero que haría dentro de 'A_foo()': 'A * me = (A *) super;' También podría convertir la función pointen cuando la asigne ('.foo = (int (*) (base *)) A_foo, ', pero creo que eso es más peligroso ya que ocultaría desajustes en otros argumentos a' A_foo() '. – cmaster

Cuestiones relacionadas