43

Straight of the bat I understand that ANSI C is not an object orientated programming language. I want to learn how to apply a particular oo technique using c.

For example, I want to create several audio effect classes that all have the same function names but different implementations of those functions.

If I was making this in a higher level language I would first write an interface and then implement it.

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


}

How can I achieve such flexibility using C?

dubbeat
  • 7,706
  • 18
  • 70
  • 122
  • See http://stackoverflow.com/questions/351733/can-you-write-object-oriented-code-in-c/351745#351745 and http://stackoverflow.com/questions/4103704/experiment-object-oriented-c/4103725#4103725 for examples. – paxdiablo Jun 10 '11 at 10:09

3 Answers3

57

There are three distinct ways you can achieve polymorphism in C:

  1. Code it out
    In the base class functions, just switch on a class type ID to call the specialized versions. An incomplete code example:

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

    Of course, this is tedious to do for large classes.

  2. Store function pointers in the object
    You can avoid the switch statements by using a function pointer for each member function. Again, this is incomplete code:

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

    Now, calling code may just do

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

    Alternatively, you may use a preprocessor macro along the lines of:

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

    Note that this evaluates the expression me twice, so this is really a bad idea. This may be fixed, but that's beyond the scope of this answer.

  3. Use a vtable
    Since all objects of a class share the same set of member functions, they can all use the same function pointers. This is very close to what C++ does under the hood:

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

    Again, this allows the user code to do the dispatch (with one additional indirection):

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

Which method you should use depends on the task at hand. The switch based approach is easy to whip up for two or three small classes, but is unwieldy for large classes and hierarchies. The second approach scales much better, but has a lot of space overhead due to the duplicated function pointers. The vtable approach requires quite a bit of additional structure and introduces even more indirection (which makes the code harder to read), but is certainly the way to go for complex class hierarchies.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • How can you set base_vtable.foo = A_foo? Presumably A_foo's signature would take an instance of A as a parameter? Wouldn't you at least need to cast somehow? – weberc2 Jan 20 '16 at 04:36
  • 1
    @weberc2 Yes, you need a cast. That would usually be the first thing you do within `A_foo()`: `A* me = (A*)super;` You could also cast the function pointen when you assign it (`.foo = (int (*)(base*))A_foo,`, but I think that's more dangerous since it would hide mismatches in other arguments to `A_foo()`. – cmaster - reinstate monica Jan 20 '16 at 06:17
  • Is there a reason A_vtable was created? Couldn't A.c just use the base vtable? – Jin Mar 01 '17 at 00:05
  • 2
    @Jin Yes: `A_vtable` can add more pointers that are not present in `base_vtable`, and which are different from additional pointers `B_vtable` may define for the use of its own subclasses. Of course, you can move everything into `base_vtable`, but then you might have to set some function pointers to `NULL`. Defining a special vtable structure for each class makes the approach more scalable and keeps the stuff that's related to `A` closer together. – cmaster - reinstate monica Mar 01 '17 at 08:10
14

Can you compromise with the following:

#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;
}
caf
  • 233,326
  • 40
  • 323
  • 462
Michael Foukarakis
  • 39,737
  • 6
  • 87
  • 123
  • 3
    this is interesting. I'm unfamiliar with this syntax "struct DelayClass delay = {.delay = 10, .ops = &operations}; " Could you please explain the dot syntax here. – dubbeat Jun 10 '11 at 11:00
  • 6
    C99 allows you to use the so called 'designated initializers' to initialize aggregate types (arrays, unions, structs). The syntax is `.member = expression`. – Michael Foukarakis Jun 10 '11 at 11:08
3

You implement interfaces using structs of function pointers. You can then have the interface struct embedded in your data object struct and pass the interface pointer as first parameter of every interface member function. In that function you then get the pointer to your container class (which is specific to your implementation) using container_of () macro. Search for "container_of linux kernel" for an implementation. It is a very useful macro.

user2826084
  • 517
  • 5
  • 11