[blog] Runtime polymorphism exploited

In this post I will be showing a dynamic polymorphism implemented in C to take a look on what C++ implements under the hood.

The idea is to have an abstract class Polygon which has a public method to calculate the area of a given polygon.

In C++ the implementation is straightforward.

//@File: main.cpp
/*using uint32 as inputs. 
you might want to use double or float.*/
#include <iostream>

class Polygon
{
public:
    virtual uint32_t area() = 0; /* pure virtual. 
implementation is delegated to derived classes*/
    virtual ~Polygon() = default;
};

class Triangle : public Polygon 
{
public:
    Triangle(uint32_t height, uint32_t base, const char* name)
    {
        this->height = height;
        this->base = base;
        this->name = name;
    }
  
private:
    uint32_t height;
    uint32_t base;
    const char* name;
    uint32_t area() override 
    {
        std::cout << "Identified " << name << " as a triangle.\n\r";
        return ((height * base) / 2);
    }
};

class Rectangle : public Polygon 
{
public:
    Rectangle(uint32_t height, uint32_t base, const char* name)
    {
        this->height = height;
        this->base = base;
        this->name = name;
    }
  
private:
    uint32_t height;
    uint32_t base;
    const char* name;
    uint32_t area() override 
    {
        std::cout << "Identified " << name << " as a rectangle.\n\r";
        return (height * base);
    }    
};
int main(void)
{
    Triangle t1{ 5,6, "polygon1"};
    Rectangle r1{ 9,5, "polygon2"};
    Polygon& p1 = t1;
    Polygon& p2 = r1;
    std::cout << "area: " << p1.area() << "\n\r";
    std::cout << "area: " << p2.area() << "\n\r";
    return 0;
}
C++ does not have an Interface keyword. You have to define interfaces through inheritance mechanisms, as we do to inherit members. To declare an interface, declare a pure virtual class. To implement an interface, derive from it.

Output

Identified polygon1 as a triangle.
area: 15
Identified polygon2 as a rectangle.
area: 45

The implementation in C can be done too. We need to know how to handle the memory structures to create the late-bind. An abstract class as “Polygon” in C , or the Base/Super class, can be seen a structure that sits on the top (the lowest address) of the memory structure of every derived class. That’s how the “is a” inheritance happens.

Note that the derived class is inheriting an i-n-t-e-r-f-a-c-e. There is a lot in common between different polygons and in how we manipulate them, so we can easily think of such an interface. Note that, in fact a triangle is a Polygon. Not every polygon is a triangle though. That's why we loosely say abstract classes can't be instantiated. 
UML class diagram and conceptual memory layout
//@File: polygon.h
#ifndef POLYGON_H 
#define POLYGON_H
#include <stdint.h>
struct VirtualTable;
typedef struct
{
	struct VirtualTable const* vptr;
} Polygon_t;
struct VirtualTable 
{
	uint32_t (*CalcArea)(Polygon_t const* const me);
};
void Polygon_Ctor(Polygon_t* const me);
//inline to save a function call
static inline uint32_t CalcArea(Polygon_t const* const me)
{
	return (*me->vptr->CalcArea)(me);
}
#endif
//@File: rectangle.h
#ifndef RECTANGLE_H
#define RECTANGLE_H
#include "polygon.h"
typedef struct
{
	Polygon_t Super; //<-Base Class
	uint32_t base;
	uint32_t height;
    const char* name;
} Rectangle_t;
void Rectangle_Ctor(Rectangle_t* this_rectangle, uint32_t base, uint32_t height, const char* name);
#endif
//@File: triangle.h
#ifndef TRIANGLE_H
#define TRIANGLE_H
#include "polygon.h"
typedef struct
{
	Polygon_t Super;
	uint32_t base;
	uint32_t height;
    const char* name;
} Triangle_t;
void Triangle_Ctor(Triangle_t* this_triangle, uint32_t base, uint32_t height, const char* name);
#endif
//@File polygon.c
#include "polygon.h"
#include <assert.h>
#include <stdlib.h>
static uint32_t CalcArea_(Polygon_t const* const me);
void Polygon_Ctor(Polygon_t* const this_polygon) 
{
	if (this_polygon != NULL) 
    {
		static struct VirtualTable const vtbl =
		{
			&CalcArea_ //pure virtual
		};
		this_polygon->vptr = &vtbl;
	}
}
static uint32_t CalcArea_(Polygon_t const* const me)
{
	assert(0); //pure virtual. should never be called
	return 0;
}

The base-class (or the Super class) has a pointer vptr to a VirtualTable structure. In this case this virtual table has a single function pointer. When called it relies on the caller to tell which function it should point to, what should be done. That is, depending on which derived class calls this function, it will perform a different computation. How is it done? When constructing the derived object you “hook” the base-class’ virtual table to a table that points to methods of the derived object. Before you have to construct the interface within the object.

//@File: triangle.c
#include <stdlib.h>
#include "polygon.h"
#include "triangle.h"
static uint32_t CalcAreaTriang_(Polygon_t const* const this_polygon);
void Triangle_Ctor(Triangle_t* this_triangle, uint32_t base, uint32_t height, const char* name)
{
	static struct VirtualTable const vtbl =
	{
		&CalcAreaTriang_ 
	};
	if (this_triangle != NULL)
	{
     //constructing the base-class: declare the interface
		Polygon_Ctor(&this_triangle->Super); 
     //binding triangle's vtable to base-class vtable: define the interface
		this_triangle->Super.vptr = &vtbl; 
    //defining class members
		this_triangle->base = base;
		this_triangle->height = height;
		this_triangle->name = name;
	}
}
static uint32_t CalcAreaTriang_(Polygon_t const* const me)
{
	Triangle_t* this_triangle = (Triangle_t*)me; //downcast
	printf("Identified %s as a triangle.\n\r", this_triangle->name);
	return ((this_triangle->height) * (this_triangle->base))/2;
}
//@File: rectangle.c
#include "polygon.h"
#include "rectangle.h"
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
static uint32_t CalcAreaRect_(Polygon_t const* const this_polygon);
void Rectangle_Ctor(Rectangle_t* this_rectangle, uint32_t base, uint32_t height, const char* name)
{
	static struct VirtualTable const vtbl = 
	{
		&CalcAreaRect_
	};
	if (this_rectangle != NULL) 
	{
		Polygon_Ctor(&this_rectangle->Super);
		this_rectangle->Super.vptr = &vtbl;
		this_rectangle->base = base;
		this_rectangle->height = height;
		this_rectangle->name = name;
	}
}
static uint32_t CalcAreaRect_(Polygon_t const* const me)
{
	Rectangle_t* this_rectangle = (Rectangle_t*)me;
	printf("Identified %s as a rectangle.\n\r", this_rectangle->name);
	return (this_rectangle->base) * (this_rectangle->height);
}

Since the private function which calculates the area receives a pointer to Polygon_t, which is at the lowest address of the structure whose area we want to compute, we perform a downcast (from Polygon_t to Triangle_t, e.g.) to access the data we need.

To use the CalcArea method we either perform an upcast on the object (e.g., from Triangle_t to Polygon_t) or access the Super class instance through the object address.

//@File: main.c
#include <stdio.h>
#include <stdlib.h>
#include "polygon.h"
#include "rectangle.h"
#include "triangle.h"
int main(void)
{
    //declaring objects
    Rectangle_t r1;
    Triangle_t t1;
    Rectangle_t r2 ;
    //constructing objects
    Rectangle_Ctor(&r1, 10, 20, "Polygon1");
    Triangle_Ctor(&t1, 5, 10, "Polygon2");
    Rectangle_Ctor(&r2, 300, 2, "Polygon3");
    //computing areas polymorphically
    printf("area: %d\n\r", CalcArea((Polygon_t*)&r1));
    printf("area: %d\n\r", CalcArea((Polygon_t*)&t1));
    printf("area: %d\n\r", CalcArea(&r2.Super));
    return 0;
}
Identified Polygon1 as a rectangle.
area: 200
Identified Polygon2 as a triangle.
area: 25
Identified Polygon3 as a rectangle.
area: 600

References

Lospinoso, Josh. C++ Crash Course. No Starch Press. Kindle Edition.
SOA para diminuição da hardware-dependência em C

Author: Antonio Giacomelli de Oliveira

Embedded Systems Engineer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: