How can I simulate OO-style polymorphism in C?

Is there a way to write OO-like code in the C programming language?


See also:

Found by searching on "[c] oo".

53812 次浏览

Common approach is to define struct with pointers to functions. This defines 'methods' which can be called on any type. Subtypes then set their own functions in this common structure, and return it.

For example, in linux kernel, there is struct:

struct inode_operations {
int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
struct dentry * (*lookup) (struct inode *,struct dentry *,
struct nameidata *);
...
};

Each registered type of filesystem then registers its own functions for create, lookup, and remaining functions. Rest of code can than use generic inode_operations:

struct inode_operations   *i_op;
i_op -> create(...);

The first C++ compiler ("C with classes") would actually generate C code, so that's definitely doable.

Basically, your base class is a struct; derived structs must include the base struct at the first position, so that a pointer to the "derived" struct will also be a valid pointer to the base struct.

typedef struct {
data member_x;
} base;


typedef struct {
struct base;
data member_y;
} derived;


void function_on_base(struct base * a); // here I can pass both pointers to derived and to base


void function_on_derived(struct derived * b); // here I must pass a pointer to the derived class

The functions can be part of the structure as function pointers, so that a syntax like p->call(p) becomes possible, but you still have to explicitly pass a pointer to the struct to the function itself.

C++ is not that far from C.

Classes are structures with a hidden pointer to a table of function pointers called VTable. The Vtable itself is static. When types point to Vtables with the same structure but where pointers point to other implementation, you get polymorphism.

It is recommended to encapsulate the calls logic in function that take the struct as parameter to avoid code clutter.

You should also encapsulcte structures instantiation and initialisation in functions (this is equivalent to a C++ constructor) and deletion (destructor in C++). These are good practice anyway.

typedef struct
{
int (*SomeFunction)(TheClass* this, int i);
void (*OtherFunction)(TheClass* this, char* c);
} VTable;


typedef struct
{
VTable* pVTable;
int member;


} TheClass;

To call the method:

int CallSomeFunction(TheClass* this, int i)
{
(this->pVTable->SomeFunction)(this, i);
}

The file functions fopen, fclose, fread are examples of OO code in C. Instead of the private data in class, they work on the FILE structure which is used to encapsulate the data and the C functions acts as an member class functions. http://www.amazon.com/File-Structures-Object-Oriented-Approach-C/dp/0201874016

Appendix B of the article Open Reusable Object Models, by Ian Piumarta and Alessandro Warth of VPRI is an implementation of an Object model in GNU C, about 140 lines of code. It's a fascinating read !

Here's the uncached version of the macro that sends messages to objects, using a GNU extension to C (statement expression):

struct object;


typedef struct object *oop;
typedef oop *(*method_t)(oop receiver, ...);


//...


#define send(RCV, MSG, ARGS...) ({ \
oop r = (oop)(RCV); \
method_t method = _bind(r, (MSG)); \
method(r, ##ARGS); \
})

In the same doc, have a look at the object, vtable, vtable_delegated and symbol structs, and the _bind and vtable_lookup functions.

Cheers!

I looked at everyone elses' answers and came up with this:

#include <stdio.h>


typedef struct
{
int (*get)(void* this);
void (*set)(void* this, int i);
int member;


} TheClass;


int Get(void* this)
{
TheClass* This = (TheClass*)this;
return This->member;
}


void Set(void* this, int i)
{
TheClass* This = (TheClass*)this;
This->member = i;
}


void init(TheClass* this)
{
this->get = &Get;
this->set = &Set;
}


int main(int argc, char **argv)
{
TheClass name;
init(&name);
(name.set)(&name, 10);
printf("%d\n", (name.get)(&name));
return 0;
}

I hope that answers some questions.

From Wikipedia: In programming languages and type theory, polymorphism (from Greek πολύς, polys, "many, much" and μορφή, morphē, "form, shape") is the provision of a single interface to entities of different types.

So I would say the only way to implement it in C is by using variadic arguments along with some (semi)automatic type info management. For example in C++ you can write (sorry for trivialness):

void add( int& result, int a1, int a2 );
void add( float& result, float a1, float a2 );
void add( double& result, double a1, double a2 );

In C, among other solutions, the best you can do is something like this:

int int_add( int a1, int a2 );
float float_add( float a1, fload a2 );
double double_add( double a1, double a2 );


void add( int typeinfo, void* result, ... );

Then you need:

  1. to implement the "typeinfo" with enums/macros
  2. to implement the latter function with stdarg.h stuff
  3. to say goodbye to C static type checking

I am almost sure that any other implementation of polymorphism should look much like this very one. The above answers, instead, seems to try to address inheritance more than polymorphism!

#include <stdio.h>


typedef struct {
int  x;
int z;
} base;


typedef struct {
base;
int y;
int x;
} derived;


void function_on_base( base * a) // here I can pass both pointers to derived and to base
{
printf("Class base [%d]\n",a->x);
printf("Class base [%d]\n",a->z);
}
void function_on_derived( derived * b) // here I must pass a pointer to the derived class
{
printf("Class derived [%d]\n",b->y);
printf("Class derived [%d]\n",b->x);
}


int main()
{
derived d;
base b;
printf("Teste de poliformismo\n");


b.x = 2;
d.y = 1;
b.z = 3;
d.x = 4;
function_on_base(&b);
function_on_base(&d);
function_on_derived(&b);
function_on_derived(&d);
return 0;
}

The output was:

Class base [3]
Class base [1]
Class base [4]
Class derived [2]
Class derived [3]
Class derived [1]
Class derived [4]

so it works, its a polymorphic code.

UncleZeiv explained about it at the beginning.

In order too build OO functionality in C, you can look at previous answers.

But, (as it has been asked in other questions redirected to this one) if you want to understand what polymorphism is, by examples in C language. Maybe I am wrong, but I can't think of anything as easy to understand as C pointers arithmetic. In my opinion, pointer arithmetic is inherently polymorphic in C. In the following example the same function (method in OO), namely the addition (+), will produce a different behavior depending on the properties of the input structures.

Example:

double a*;
char str*;


a=(double*)malloc(2*sizeof(double));
str=(char*)malloc(2*sizeof(char));


a=a+2; // make the pointer a, point 2*8 bytes ahead.


str=str+2; // make the pointer str, point 2*1 bytes ahead.

Disclaimer: I am very new at C and very much looking forward to being corrected and learn from other user's comments, or even completely erase this answer, should it be wrong. Many thanks,

What I usually like to do is to wrap the structs in another which contain meta information about the wrapped class and then build visitor like function lists acting on the generic struct. The advantage of this approach is that you don't need to modify the existing structures and you can create visitors for any subset of structs.

Take the usual example:

typedef struct {
char call[7] = "MIAO!\n";
} Cat;
    

typedef struct {
char call[6] = "BAU!\n";
} Dog;

We can wrap the 2 strutures in this new structure:

typedef struct {
const void * animal;
AnimalType type;
} Animal;

The type can be a simple int but let's not be lazy:

typedef enum  {
ANIMAL_CAT = 0,
ANIMAL_DOG,
ANIMAL_COUNT
} AnimalType;

It would be nice to have some wrapping functions:

Animal catAsAnimal(const Cat * c) {
return (Animal){(void *)c, ANIMAL_CAT};
}


Animal dogAsAnimal(const Dog * d) {
return (Animal){(void *)d, ANIMAL_DOG};
}

Now we can define our "visitor":

void catCall ( Animal a ) {
Cat * c = (Cat *)a.animal;
printf(c->call);
}


void dogCall ( Animal a ) {
Dog * d = (Dog *)a.animal;
printf(d->call);
}


void (*animalCalls[ANIMAL_COUNT])(Animal)={&catCall, &dogCall};

Then the actual usage will be:

Cat cat;
Dog dog;


Animal animals[2];
animals[0] = catAsAnimal(&cat);
animals[1] = dogAsAnimal(&dog);


for (int i = 0; i < 2; i++) {
Animal a = animals[i];
animalCalls[a.type](a);
}

The disadvantage of this approach is that you have to wrap the structures every time you want to use it as a generic type.

A very crude example of simple function overloading, much can be achieved using variadic macros.

#include <stdio.h>
#include <stdlib.h>


#define SCOPE_EXIT(X) __attribute__((cleanup (X)))


struct A
{
int a;
};


struct B
{
int a, b;
};


typedef struct A * A_id;
typedef struct B * B_id;




A_id make_A()
{
return (A_id)malloc(sizeof(struct A));
}


void destroy_A(A_id * ptr)
{
free(*ptr);
*ptr = 0;
}


B_id make_B()
{
return (B_id)malloc(sizeof(struct B));
}


void destroy_B(B_id * ptr)
{
free(*ptr);
*ptr = 0;
}


void print_a(A_id ptr)
{
printf("print_a\n");
}
void print_b(B_id ptr)
{
printf("print_b\n");
}


#define print(X) _Generic((X),\
A_id : print_a, \
B_id : print_b\
)(X)


int main()
{
A_id aa SCOPE_EXIT(destroy_A) = make_A();
print(aa);


B_id bb SCOPE_EXIT(destroy_B) = make_B();
print(bb);
return 0;
}

Different implementations of functions is one of the key features of polymorphism, so you must use function pointers.

animal.h

typedef struct Animal {
const void (*jump)(struct Animal *self);
} Animal;

pig.h

#include "animal.h"


typedef struct {
Animal animal_interface;
char *name;
} Pig;


Pig *NewPig(char *name);

pig.c

#include <stdio.h>
#include <stdlib.h>
#include "pig.h"


static void PigJump(Animal *_self) {
Pig *self = (Pig *)_self;
printf("%s Pig jump.\n", self->name);
}


Pig *NewPig(char *name) {
Pig *self = (Pig *)malloc(sizeof(Pig));
self->animal_interface.jump = PigJump;
self->name = name;
return self;
}

main.c

#include "pig.h"


int main() {
Animal *a = &(NewPig("Peppa")->animal_interface);
Animal *b = &(NewPig("Daddy")->animal_interface);
a->jump(a);
b->jump(b);
return 0;
}

Output:

Peppa Pig jump.
Daddy Pig jump.