ООП и интерфейсы в C - PullRequest
       1

ООП и интерфейсы в C

28 голосов
/ 10 июня 2011

Прямо с рук Я понимаю, что ANSI C не является объектно-ориентированным языком программирования.Я хочу узнать, как применять конкретную технику oo, используя c.

Например, я хочу создать несколько классов звуковых эффектов, которые имеют одинаковые имена функций, но разные реализации этих функций.

Если бы я делал это на языке более высокого уровня, я сначала написал бы интерфейс, а затем реализовал его.

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?

Ответы [ 3 ]

27 голосов
/ 17 декабря 2014

Существует три различных способа достижения полиморфизма в C:

  1. Кодируйте его
    В функциях базового класса просто switch наидентификатор типа класса для вызова специализированных версий.Пример неполного кода:

    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();
        }
    }
    

    Конечно, этот утомителен для больших классов.

  2. Хранить указатели на функциив объекте
    Вы можете избежать операторов switch, используя указатель функции для каждой функции-члена.Опять же, это неполный код:

    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;
    }
    

    Теперь вызывающий код может просто выполнить

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

    В качестве альтернативы, вы можете использовать макрос препроцессора в следующих строках:

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

    Обратите внимание, что это вычисляет выражение me дважды, так что это действительно плохая идея.Это может быть исправлено, но это выходит за рамки этого ответа.

  3. Использование виртуальной таблицы
    Поскольку все объекты класса совместно используют один и тот же набор членовфункции, все они могут использовать одни и те же указатели функций.Это очень близко к тому, что делает C ++ под капотом:

    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;
    };
    

    Опять же, это позволяет пользовательскому коду выполнять диспетчеризацию (с одним дополнительным косвенным указанием):

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

Какой метод вы должны использовать, зависит от поставленной задачи.Подход, основанный на switch, легко реализовать для двух или трех небольших классов, но неудобен для больших классов и иерархий.Второй подход масштабируется намного лучше, но имеет много места из-за дублированных указателей функций.Подход vtable требует совсем немного дополнительной структуры и вносит еще большую косвенность (что делает код более трудным для чтения), но, безусловно, является подходом для сложных иерархий классов.

11 голосов
/ 10 июня 2011

Можете ли вы пойти на компромисс со следующим:

#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;
}
3 голосов
/ 17 декабря 2014

Вы реализуете интерфейсы, используя структуры указателей функций.Затем вы можете встроить структуру интерфейса в структуру объекта данных и передать указатель интерфейса в качестве первого параметра каждой функции-члена интерфейса.В этой функции вы затем получаете указатель на ваш контейнерный класс (который специфичен для вашей реализации) с помощью макроса container_of ().Поиск "контейнер_ ядра Linux" для реализации.Это очень полезный макрос.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...