Inheritance and Polymorphism are two important concepts of Object Oriented Programming (OOP). A procedural language like C does not support this concepts by nature. Is it possible to implement them in another way?
Inheritance in C is implemented by nested structs and manually placed base class functions. This servers as the basis for Polymorphism, together with function pointers and a disciplined naming convention.
We will see how this works in detail in the rest of this article.
How to Implement Inheritance in C?
The goal of Inheritance is the re-usability of already implemented code by grouping common functionality of different classes in so called base classes. An example is that all shapes share a x and y position but differ in other things like width, height or radius. Circle and Rectangle could be derived from Shape and therefore inherit it’s public attributes and methods.
As C is no OOP language there is no concept of a class or interface. In order to use this principle we have to build it on our own. The first step is to emulate a class in C and then we are able to implement inheritance.
For the attributes you use nested structs which means that you put the struct of your base “class” into the struct of your derived “class”. It has to be the first member of this struct. For the methods you will have to follow a disciplined naming convention (to keep track) and place them manually at the appropriate places within the derived classes.
To get a better idea we will now look at an example.
Example: Inheritance in C
I use my personal Equivalent to Classes in C in this example. If you want to know more about how this works and how to implement, I have another article for you.
First we define a base class that we want to inherit from:
Shape
#pragma once
struct shape_t {
int x;
int y;
};
void shape_ctor(struct shape_t* obj, const int x, const int y);
void shape_print_position(struct shape_t* obj);
#include "shape.h"
#include <stdio.h>
void shape_ctor(struct shape_t* obj, const int x, const int y) {
obj->x = x;
obj->y = y;
}
void shape_print_position(struct shape_t* obj) {
printf("X:%d|Y:%d\n", obj->x, obj->y);
}
Now we can derive our child classes from this:
Rectangle
#include "shape.h"
struct rectangle_t {
struct shape_t base; /* Reference to Base Class */
int width;
int height;
};
typedef int bool;
void rectangle_ctor(struct rectangle_t* obj, const int x, const int y,
const int width, const int height);
bool rectangle_is_square(struct rectangle_t* obj);
#include "rectangle.h"
void rectangle_ctor(struct rectangle_t* obj, const int x, const int y,
const int width, const int height) {
shape_ctor(&obj->base, x, y); /* Call Base Class Constructor */
obj->width = width;
obj->height = height;
}
bool rectangle_is_square(struct rectangle_t* obj) {
return (obj->height == obj->width);
}
Circle
#include "shape.h"
struct circle_t {
struct shape_t base; /* Reference to Base Class */
int radius;
};
void circle_ctor(struct circle_t* obj, const int x, const int y, const int radius);
#include "circle.h"
void circle_ctor(struct circle_t* obj, const int x, const int y, const int radius) {
shape_ctor(&obj->base, x, y); /* Call Base Class Constructor */
obj->radius = radius;
}
How to Implement Polymorphism in C?
In Polymorphism we declare an Interface and implement the details in entities of different types. In our example above, for example, we could have a draw function in the shape “class”. But the real code how to draw the shape depends on the class that implements this function – drawing a rectangle is not the same as drawing a circle.
This way we can create different objects but calling the same behaviour through its interface functions. If we want to draw all shapes every 30 frames, we just call draw of the interface and don’t have to care about if there are rectangles, circles or triangles to draw.
Polymorphism in C++
In order to understand this principle we will take a look at C++ where polymorphism is part of the language. If we had to implement a polymorph draw function in C++, we could do it like this:
class Shape {
public:
/* ... */
virtual void draw() { /* do nothing */ };
};
class Rectangle : public Shape {
public:
/* ... */
void draw() override { /* draw Rectangle */ };
};
class Circle : public Shape {
public:
/* ... */
void draw() override { /* draw Circle */ };
};
In fact, if we really wanted Shape to be a pure interface, we would make all functions pure virtual so that no implementation of draw for Shape is needed, but also no Shape object can be constructed.
class Shape {
public:
/* ... */
virtual void draw() = 0; /* This pure virtual function turns "Shape" into an Interface */
};
The keyword to make polymorphism possible in C++ is virtual. Every object has a Virtual Method Table (or VTable) where all addresses of its dynamically bound functions. It basically is an array of pointers. When the compiler “sees” the virtual keyword, it generates a hidden private variable that points to this array. This makes it possible to bind the functions at runtime.
Polymorphism in C
In C there is no VTable because there are no objects that would have them in the first place. If we want to implement polymorphism in C we therefore have to create and manage these Virtual Method Tables ourselves. The way to dynamically bind functions in C is per function pointers.
Shape
#pragma once
struct shape_t {
/* Our manual Virtual Method Table */
const struct shape_interface* const vtable;
};
struct shape_interface {
/* All virtual Methods of the Interface */
void (*draw)();
/* ... could be more ... */
};
void shape_draw(struct shape_t* obj); /* Function to Call */
#include "shape.h"
void shape_draw(struct shape_t* obj) {
/* Call draw of the real Instance */
obj->vtable->draw();
}
Rectangle
#pragma once
#include "shape.h"
struct rectangle_t {
struct shape_t* base; /* Reference to Base Class */
/* Rectangle specific Members */
int x;
int y;
};
struct shape_t* shape_create_rectangle();
#include "rectangle.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void draw() {
printf("DRAW RECTANGLE\n");
}
struct shape_t* shape_create_rectangle() {
static const struct shape_interface_t vtable = { draw };
static struct shape_t base = {&vtable};
struct rectangle_t* rectangle = malloc(sizeof(*rectangle));
memcpy(&rectangle->base, &base, sizeof(base));
return (struct shape_t*)(&rectangle->base);
}
Circle
#pragma once
#include "shape.h"
struct circle_t {
struct shape_t* base; /* Reference to Base Class */
/* Circle specific Members */
int radius;
};
struct shape_t* shape_create_circle();
#include "circle.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void draw() {
printf("DRAW CIRCLE\n");
}
struct shape_t* shape_create_circle() {
static const struct shape_interface_t vtable = { draw };
static struct shape_t base = {&vtable};
struct circle_t* circle = malloc(sizeof(*circle));
memcpy(&circle->base, &base, sizeof(base));
return (struct shape_t*)(&circle->base);
}
Polymorphism in C in Action
So now we put the whole thing together and create a circle and a rectangle. We create general shape objects and specify the type through initialization. We can then simply call the draw function and the polymorphism takes care of the rest.
#include "rectangle.h"
#include "circle.h"
int main() {
struct shape_t* rectangle = shape_create_rectangle();
struct shape_t* circle = shape_create_circle();
shape_draw(rectangle);
shape_draw(circle);
return 0;
}
Now you know how to implement two important OOP concepts in the procedural C programming language.