Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Inheritance and Polymorphism in C

4.74/5 (35 votes)
10 Sep 2010CPOL5 min read 243.2K   1.3K  
Implementation of single level inheritance and polymorphism in C.

Introduction

Inheritance and polymorphism are the most powerful features of Object Oriented Programming Languages. With inheritance and polymorphism, we can achieve code reuse.

There are many tricky ways for implementing polymorphism in C.

The aim of this article is to demonstrate a simple and easy technique of applying inheritance and polymorphism in C. By creating a VTable (virtual Table) and providing proper access between base and derived objects, we can achieve inheritance and polymorphism in C. The concept of VTable can be implemented by maintaining a table of pointers to functions. For providing access between base and derived objects, we have to maintain the references of derived object in the base class and the reference of the base object in the derived class.

Description

Before proceeding to the implementation of inheritance and polymorphism in C, we should know the class representation in C.

Class representation in C

Consider a class “Person” in C++.

C++
//Person.h
class Person
{
private:
    char* pFirstName;
    char* pLastName;
    
public:
    Person(const char* pFirstName, const char* pLastName);    //constructor
    ~Person();    //destructor

    void displayInfo();
    void writeToFile(const char* pFileName);

};

For representing the above class in C, we can use structures, and functions which operate on that structure as member functions.

C++
//Person.h
typedef struct _Person
{
    char* pFirstName;
    char* pLastName;
}Person;

new_Person(const char* const pFirstName, const char* const pLastName);    //constructor
delete_Person(Person* const pPersonObj);    //destructor

void Person_DisplayInfo(Person* const pPersonObj);
void Person_WriteToFile(Person* const pPersonObj, const char* const pFileName);

Here, the functions defined for the structure Person are not encapsulated. For implementing encapsulation, which is binding between data and functions, pointers to functions are used. We need to create a table of function pointers. The constructor new_Person() will set the values of the function pointers to point to the appropriate functions. This table of function pointers will then act as an interface for accessing the functions through the object.

Let us redefine the C implementation of the class Person.

C++
//Person.h

typedef struct _Person Person;

//declaration of pointers to functions
typedef void    (*fptrDisplayInfo)(Person*);
typedef void    (*fptrWriteToFile)( Person*, const char*);
typedef void    (*fptrDelete)( Person *) ;

//Note: In C all the members are by default public. We can achieve 
//the data hiding (private members), but that method is tricky. 
//For simplification of this article
// we are considering the data members     //public only.
typedef struct _Person 
{
    char* pFName;
    char* pLName;
    //interface for function
    fptrDisplayInfo   Display;
    fptrWriteToFile   WriteToFile;
    fptrDelete      Delete;
}Person;

person* new_Person(const char* const pFirstName, 
                   const char* const pLastName); //constructor
void delete_Person(Person* const pPersonObj);    //destructor

void Person_DisplayInfo(Person* const pPersonObj);
void Person_WriteToFile(Person* const pPersonObj, const char* pFileName);

The new_Person() function acts as a constructor. The function returns the newly created instance of the structure. It initializes the interface of the function pointers to access other member functions. One thing to note here is, we are defining only those function pointers which are available for public access. We have not given access to private functions in the interface. Let us take a look at the new_Person() function or the constructor of our Person class in C.

C++
//Person.c
person* new_Person(const char* const pFirstName, const char* const pLastName)
{
    Person* pObj = NULL;
    //allocating memory
    pObj = (Person*)malloc(sizeof(Person));
    if (pObj == NULL)
    {
        return NULL;
    }
    pObj->pFirstName = malloc(sizeof(char)*(strlen(pFirstName)+1));
    if (pObj->pFirstName == NULL)
    {
        return NULL;
    }
    strcpy(pObj->pFirstName, pFirstName);

    pObj->pLastName = malloc(sizeof(char)*(strlen(pLastName)+1));
    if (pObj->pLastName == NULL)
    {
        return NULL;
    }
    strcpy(pObj->pLastName, pLastName);

    //Initializing interface for access to functions
    pObj->Delete = delete_Person;
    pObj->Display = Person_DisplayInfo;
    pObj->WriteToFile = Person_WriteToFile;

    return pObj;
}

After the object creation, we can access its data members and functions.

C++
Person* pPersonObj = new_Person("Anjali", "Jaiswal");
//displaying person info
pPersonObj->Display(pPersonObj);
//writing person info in the persondata.txt file
pPersonObj->WriteToFile(pPersonObj, "persondata.txt");
//delete the person object
pPersonObj->Delete(pPersonObj);
pPersonObj = NULL;

Note: Unlike in C++, in C, we cannot access data members directly in the functions. In C++, data members can be directly accessed implicitly through the “this” pointer. As we do not have the “this” pointer in C, we pass the object explicitly to the member function. For accessing the data members of a class in C, we need to pass the calling object as an argument to the function. In the above example, we are passing the calling object as a first argument to the function. This way, the function can access the data members of the object.

Representation of the class in C

The Person class representation - check the initialization of the interface to point to the member functions:

Image 1

Simple example of inheritance and polymorphism

Inheritance - class Employee derived from class Person:

Image 2

In the above example, class Employee inherits the properties of class Person. As DisplayInfo() and WriteToFile() functions are virtual, we can access the same functions for the Employee object from the Person instance. For this, we need to initiate the Person instance with the Employee class. This is possible because of polymorphism. In the case of polymorphism, to resolve the function call, C++ makes use of the VTable, which is nothing but a table of pointers to functions.

The interface of pointers to functions that we are maintaining in the structure works similar to the VTable.

C++
//Polymorphism in C++
Person PersonObj("Anjali", "Jaiswal");
Employee EmployeeObj("Gauri", "Jaiswal", "HR", "TCS", 40000);

Person* ptrPersonObj = NULL;
    
//preson pointer pointing to person object
ptrPersonObj = &PersonObj;
//displaying person info
ptrPersonObj ->Display();
//writing person info in the persondata.txt file
ptrPersonObj ->WriteToFile("persondata.txt");

//preson pointer pointing to employee object
ptrPersonObj = &EmployeeObj;
//displaying employee info
ptrPersonObj ->Display();
//writing empolyee info in the employeedata.txt file
ptrPersonObj ->WriteToFile("employeedata.txt");

In C, inheritance can be achieved by maintaining a reference to the base class object in the derived class object. With the help of the base class' instance, we can access the base data members and functions. However, in order to achieve polymorphism, the base class object should be able to access the derived class object’s data. For this, the base class should have access rights to the derived class’ data members.

For implementing the virtual function, the signature of the derived class’ function has to be similar to the base class’ function pointers. That means the derived class function will take the instance of the base class as a parameter. We are maintaining the reference to the derived class in the base class. During function implementation, we can access the actual derived class data from the reference of the derived class.

Equivalent representation in C structures

Inheritance in C - Person and Employee structure in C:

Image 3

As shown in the diagram, we have declared a pointer in the base class structure holding the derived class object, and a pointer in the derived class structure holding the base class object.

In the base class object, the function pointers point to the virtual functions of its own class. In the derived class object’s construction, we need to make the interface of the base class to point to the member function of the derived class. This gives us the flexibility of calling the derived class function through the base class object (polymorphism). For more details, check the construction on the Person and Employee objects.

When we talk about polymorphism in C++, there is a problem of object destruction. For the proper cleanup of objects, it uses virtual destructors. In C, this can be done by making the delete function pointer of the base class to point to the derived class destructor function. The derived class' destructor cleans up the derived class data as well as the base class data and object. Note: Check the sample source code for details about the implementation of the virtual destructor and the virtual functions.

Creating the Person object
C++
//Person.h

typedef struct _Person Person;

//pointers to function
typedef void    (*fptrDisplayInfo)(Person*);
typedef void    (*fptrWriteToFile)(Person*, const char*);
typedef void    (*fptrDelete)(Person*) ;

typedef struct _person
{
    void* pDerivedObj;
    char* pFirstName;
    char* pLastName;
    fptrDisplayInfo Display;
    fptrWriteToFile WriteToFile;
    fptrDelete        Delete;
}person;

Person* new_Person(const char* const pFristName, 
                   const char* const pLastName);    //constructor
void delete_Person(Person* const pPersonObj);    //destructor

void Person_DisplayInfo(Person* const pPersonObj);
void Person_WriteToFile(Person* const pPersonObj, const char* const pFileName);
    
//Person.c
//construction of Person object
Person* new_Person(const char* const pFirstName, const char* const pLastName)
{
    Person* pObj = NULL;
    //allocating memory
    pObj = (Person*)malloc(sizeof(Person));
    if (pObj == NULL)
    {
        return NULL;
    }
    //pointing to itself as we are creating base class object
    pObj->pDerivedObj = pObj;
    pObj->pFirstName = malloc(sizeof(char)*(strlen(pFirstName)+1));
    if (pObj->pFirstName == NULL)
    {
        return NULL;
    }
    strcpy(pObj->pFirstName, pFirstName);

    pObj->pLastName = malloc(sizeof(char)*(strlen(pLastName)+1));
    if (pObj->pLastName == NULL)
    {
        return NULL;
    }
    strcpy(pObj->pLastName, pLastName);

    //Initializing interface for access to functions
    //destructor pointing to destrutor of itself
    pObj->Delete = delete_Person;
    pObj->Display = Person_DisplayInfo;
    pObj->WriteToFile = Person_WriteToFile;

    return pObj;
}
Struture of the Person object

creating person

Creating the Employee object
C++
//Employee.h

#include "Person.h"

typedef struct _Employee Employee;

//Note: interface for this class is in the base class
//object since all functions are virtual.
//If there is any additional functions in employee add
//interface for those functions in this structure 
typedef struct _Employee
{
    Person* pBaseObj;
    char* pDepartment;
    char* pCompany;
    int nSalary;
    //If there is any employee specific functions; add interface here.
}Employee;

Person* new_Employee(const char* const pFirstName, const char* const pLastName,
        const char* const pDepartment, const char* const pCompany, 
        int nSalary);    //constructor
void delete_Employee(Person* const pPersonObj);    //destructor

void Employee_DisplayInfo(Person* const pPersonObj);
void Employee_WriteToFile(Person* const pPersonObj, const char* const pFileName);
    
//Employee.c
Person* new_Employee(const char* const pFirstName, const char* const pLastName,
                     const char* const pDepartment, 
                     const char* const pCompany, int nSalary)
{
    Employee* pEmpObj;
    //calling base class construtor
    Person* pObj = new_Person(pFirstName, pLastName);
    //allocating memory
    pEmpObj = malloc(sizeof(Employee));
    if (pEmpObj == NULL)
    {
        pObj->Delete(pObj);
        return NULL;
    }
    pObj->pDerivedObj = pEmpObj; //pointing to derived object
    
    //initialising derived class members
    pEmpObj->pDepartment = malloc(sizeof(char)*(strlen(pDepartment)+1));
    if(pEmpObj->pDepartment == NULL)
    {
        return NULL;
    }
    strcpy(pEmpObj->pDepartment, pDepartment);
    pEmpObj->pCompany = malloc(sizeof(char)*(strlen(pCompany)+1));
    if(pEmpObj->pCompany== NULL)
    {
        return NULL;
    }
    strcpy(pEmpObj->pCompany, pCompany);
    pEmpObj->nSalary = nSalary;
        
    //Changing base class interface to access derived class functions
    //virtual destructor
    //person destructor pointing to destrutor of employee
    pObj->Delete = delete_Employee;
    pObj->Display = Employee_DisplayInfo;
    pObj->WriteToFile = Employee_WriteToFile;

    return pObj;
}
Struture of the Employee object

Image 5

Note: Changed the pointing position of the interface (VTable) from base class functions to derived class functions. Now we can access the derived class functions from the base class (polymorphism). Let us now look at how we can use polymorphism.

C++
Person* PersonObj = new_Person("Anjali", "Jaiswal");
Person* EmployeeObj = new_Employee("Gauri", "Jaiswal","HR", "TCS", 40000);

//accessing person object

//displaying person info
PersonObj->Display(PersonObj);
//writing person info in the persondata.txt file
PersonObj->WriteToFile(PersonObj,"persondata.txt");
//calling destructor
PersonObj->Delete(PersonObj);

//accessing to employee object

//displaying employee info
EmployeeObj->Display(EmployeeObj);
//writing empolyee info in the employeedata.txt file
EmployeeObj->WriteToFile(EmployeeObj, "employeedata.txt");
//calling destrutor
EmployeeObj->Delete(EmployeeObj);

Conclusion

Involving the above described simple addition of code can grant the procedural C language a flavor of polymorphism and inheritance. We simply use function pointers for creating a VTable and maintain cross references of objects for accessibility among base and derived objects. With these simple steps, we can implement Inheritance and Polymorphism in C.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)