Technique: Inheritance and Polymorphism in C

While C is not an object-oriented language, objects, inheritance, and polymorphism can be implemented in C.

Table of Contents:

  1. Inheritance
  2. Polymorphism
  3. Further Reading

Inheritance

The core idea behind inheritance relies on structures. A base class or API can be represented by a struct definition that includes all relevant interfaces and data members.

For example, consider this example of an event base “class” from an Electron Vector article:

typedef enum {
  EVENT_BUTTON_PRESSED,
  EVENT_BUTTON_RELEASED,
  EVENT_KNOB_SET,
  EVENT_SWITCH_SET,
} event_type_t;

typedef struct {
  event_type_t type;
} event_t;

To simulate inheritance in C, we define additional event structures which include the base structure as the first element. The base structure must always be the first element.

typedef struct {
  event_t event;
  button_t button;
} event_button_pressed_t;

typedef struct {
  event_t event;
  button_t button;
} event_button_released_t;

The key detail is that a pointer to the derived structures (event_button_pressed_t) is also a pointer to the base structure (event_t). You can safely cast between the two structures, and any derived type can be used with APIs that require only the base type.

Just remember this foundational pattern, outlined in Dmitry Frank’s article:

/* base class */
typedef struct S_Base {
   /* ... */
} T_Base;
 
/* derived class */
typedef struct S_Derived {
   T_Base base;
   /* ... */
} T_Derived

Polymorphism

Polymorphism is the ability to substitute objects using matching interfaces for one-another at runtime. With C++, polymorphism is handled through virtual interfaces and the virtual table (vtable). The example code used below is adapted from Miro Samek’s excellent overview.

For example, we can define a base “class” for a shape, along with a vtable which contains function pointers. Derived classes populate the vtable pointers with functions that are specific to the derived class.

struct ShapeVTable; // Forward declare the vtable

// Base class definition
typedef struct {
	struct ShapeVTable const *vptr;
	int32_t x; // x-coord of shape's position
	int32_t y; // y-coord of shape's position
} Shape; 

struct ShapeVTable
{
	uint32_t (*area)(Shape const * const me);
	void (*draw)(Shape const * const me);
};

Non-virtual functions for the base class are implemented as a standard functions which take a base class pointer as the first parameter:

/* Shape's operations (Shape's interface)... */
void Shape_ctor(Shape * const me, int16_t x, int16_t y); 
void Shape_moveBy(Shape * const me, int16_t dx, int16_t dy);

One such required non-virtual function is a constructor, whose most important job is to supply the proper vtable for the class:

void Shape_ctor(Shape * const me, int16_t x, int16_t y) {
	static struct ShapeVtbl const vtbl = { /
		* vtbl of the Shape class */ 
		&Shape_area_,
		&Shape_draw_ 
	};
	me->vptr = &vtbl; /* "hook" the vptr to the vtbl */ 
	me->x = x;
	me->y = y;

Any base class or derived function can reference the virtual functions using the vtable:

static inline uint32_t Shape_area(Shape const * const me) 
{ 
	return (*me->vptr->area)(me);
}

To derive from the base class, we simply use the base class Shape as the first element of the derived structure:

typedef struct {
	Shape base; /* <== inherits Shape */
	/* attributes added by this subclass... */ 
	uint16_t width;
	uint16	_t height;
} Rectangle;

As a result, we can cast Rectangle so it can be used with Shapeinterfaces. We can also create Rectangle-specific interfaces.

Of course, our derived class needs its own constructor:

void Rectangle_ctor(Rectangle * const me, int16_t x, int16_t y,
	uint16_t width, uint16_t height)
{
	static struct ShapeVtbl const vtbl = { 
		/* vtbl of the Rectangle class */ 
		&Rectangle_area_,
		&Rectangle_draw_
	};
	
	// We always call the base class constructor
	Shape_ctor(&me->base, x, y);
	
	// But we must override the base vtable with Rectangle's
	me->base.vptr = &vtbl; 
	
	// Rectangle-specific values
	me->width = width;
	me->height = height;
}

Because we defined a new class, we need Rectangle-specific implementations of the virtual functions. These are added to a new vtable.

Whenever a Rectangle is constructed, the base class constructor is called first. Then, the vtable is set to Rectangle’s table. Any virtual function calls will dereference the proper Rectangle-specific implementation, even when using base class interfaces.

This is the basic overview of implementing polymorphism and virtual functions in C. For a detailed overview of implementing full polymorphism, see the links in Further Reading.

As a final note, if you are relying on these concepts in your C programs, you will likely benefit from switching to C++, if only for the fact that the compiler handles so much of this overhead for you. Additionally, you can rely on optimizations from the C++ compiler that won’t work in the C implementation.

Further Reading

For more on Inheritance, Polymorphism, and OOP in C:

Share Your Thoughts

This site uses Akismet to reduce spam. Learn how your comment data is processed.