D   A   T   A   W   O   K





Creation: September 18 2018
Modified: February 05 2022

Polymorphism in C

This article is part of a series that faces the implementation of four of the most fundamental concepts of object-oriented programming using the standard C programming language:

I assume that you are already familiar with object-oriented programming. Thus, for every subject, a very short theoretical introduction is given.


Polymorphism is the provision of a single interface to entities of different types.

Let's assume that at we have a list of persons. Each person can be a simple person (base class) or a student (derived class).

We want to provide a mechanism such that when we invoke a function that both types have, depending on the fact that the person is a student or not, the answer is different.

To implement such a feature, commonly any Object Oriented language defines, within the base class, a table of function pointers (often called the vtable), and a special "wrapper" function for every function pointer within that table.

The wrapper functions are used as a trampoline to invoke the correct function using the correspondent function pointer in the vtable.

Such an indirectly called function is called a virtual function.

To implement the feature with the C language we borrow the ideas used by the OO languages implementations.

Starting from the "inheritance" example, we modify the base class, person, to hold a pointer to table of function pointers:

typedef struct person {
    const void  *vtab;      /* virtual table */
    char        *name;
} person_t;

The student structure is then defined as a person derivate.

typedef struct student {
    person_t    super;      /* superclass */
    int         number;
} student_t;

For each subtype, then, if we desire to overload a particular function that is also present in the base type, we must define a virtual table instance and assign it to the base class vtab pointer.

Note that, for each type, the virtual table is usually instanced only once and is shared between all the type instances.

Virtual table definition for person and for student types.

/* Virtual functions pointers type alias */
typedef void (*person_destroy_f)(person_t *self);
typedef void (*person_print_f)(const person_t *self);

/* Person virtual table structure */
typedef struct person_vtab {
    person_destroy_f destroy;
    person_print_f   print;
} person_vtab_t;

Person virtual table, constructor and destructor:

static const person_vtab_t person_vtab = {
    person_destroy,
    person_print
};

void person_construct(person_t *person, const char *name)
{
    person->vtab = &person_vtab;    /* assign the person vtab */
    person->name = malloc(strlen(name) + 1);
    strcpy(person->name, name);
}

void person_destroy(person_t *person)
{
    free(person->name);
    person->name = NULL;
}

void person_print(person_t *person)
{
    printf("I'm a person\n");
}

Student virtual table, constructor and destructor:

static const student_vtab_t student_vtab = {
    (person_delete_f) student_destroy,
    (person_print_f)  student_print
};

void student_construct(student_t *student, const char *name, int number)
{
    person_construct(&student->super, name); /* construct the superclass */
    student->super.vtab = &student_vtab;     /* set the student vtab */
    student->number = number;
}

void student_destroy(student_t *student)
{
    person_destroy(&student->super);         /* destroy the superclass */
    /* Eventually destroy resources managed by the subclass */
}

void student_print(student_t *student)
{
    printf("I'm a student");
}

Note that the constructor is not part of the virtual table.

Wrappers to the "virtual functions" are finally needed to implement the runtime polymorphic behavior.

The type specific functions are invoked via the functions pointers that are stored within the specific object instance vtable.

void person_vdestroy(person_t *person)
{
    ((person_vtab_t *)person->vtab)->destroy(person);
}

void person_vprint(const person_t *person)
{
    ((person_vtab_t *)person->vtab)->print(person);
}

Usage example:

person_t *stud = (person_t *) student_create("Davide", 3801831);
person_vprint(stud);    /* calls student_print */
person_vdelete(stud);   /* calls student_destroy */

Example sources download

Proudly self-hosted on a cheap Raspberry Pi