Virtual Functions in C

Original author: Milot Shala
  • Transfer
Recently I was asked a question: how would I implement the mechanism of virtual functions in C?

At first, I had no idea how to do this: after all, C is not an object-oriented programming language, and there is no such thing as inheritance. But since I already had some experience with C, and I knew how virtual functions worked, I thought there should be a way to mimic the behavior of virtual functions using structures (struct).

A brief explanation for those who do not know what virtual functions are:
A virtual function is a function that can be overridden by the successor class so that it has its own, different, implementation. C ++ uses a mechanism such as a virtual function table
(briefly vtable) in order to support binding at the program execution stage. A virtual table is a static array that stores for each virtual function a pointer to the closest implementation of this function in the inheritance hierarchy. The closest implementation in the hierarchy is determined at run time by retrieving the function address from the object's method table.


Let's now look at a simple example of using virtual functions in C ++

class ClassA
{
public:
    ClassA() {data = 10;}
    virtual void set()
    {
        std::cout << "ClassA is increasing" << std::endl;
        data++;
    }
    int get()
    {
        set();
        return data;
    }
protected:
    int data;
};
class ClassB : public ClassA
{
public:
    void set()
    {
        std::cout << "ClassB is decreasing" << std::endl;
        data--;
    }
};

In the above example, we have a class ClassAthat has two methods: get()and set(). The method is get()marked as a virtual function; in a class, ClassBits implementation is changing. An integer is datamarked with a keyword protectedso that the derived class has ClassBaccess to it.


int main()
{
    ClassA classA;
    ClassB classB;
    std::cout << "ClassA value: " << classA.get() << std::endl;
    std::cout << "ClassB value: " << classB.get() << std::endl;
    return 0;
}

Conclusion:

ClassA is increasing
ClassA value: 11
ClassB is decreasing
ClassB value: 9

Now let's think about how to implement the concept of virtual functions in C. Knowing that virtual functions are represented as pointers and stored in vtable, and vtable is a static array, we need to create a structure that simulates the ClassA class itself, the virtual function table for ClassA, and implementation of ClassA methods.

/* Опережающее объявление структуры */
struct ClassA;
/* Таблица функций, хранящая указатели на функции. */
typedef struct {
    void (*ClassA)(struct ClassA*); /* "конструктор" */
    void (*set)(struct ClassA*); /* функция set */
    int (*get)(struct ClassA*); /* функция get */
} ClassA_functiontable;
typedef struct ClassA {
    int data;
    ClassA_functiontable *vtable; /* Таблица виртуальных функций ClassA */
} ClassA;
/* Прототипы методов ClassA */
void ClassA_constructor(ClassA *this);
void ClassA_set(ClassA *this);
int ClassA_get(ClassA *this);
/* Инициализация таблицы виртуальных функций ClassA */
ClassA_functiontable ClassA_vtable = {ClassA_constructor,
                                  ClassA_set,
                                  ClassA_get };
/* Реализации методов */
void ClassA_constructor(ClassA *this) {
    this->vtable = &ClassA_vtable;
    this->data = 10;
}
void ClassA_set(ClassA *this) {
    printf("ClassA is increasing\n");
    this->data++;
}
int ClassA_get(ClassA *this) {
    this->vtable->set(this);
    return this->data;
}

In C, there is no pointer thisthat points to the caller. I named the parameter like this in order to simulate using a pointer thisin C ++ (in addition, it looks like the way an object method call in C ++ actually works).

As we can see from the code above, the implementation ClassA_get()calls the function set()through a pointer from vtable. Now let's look at the implementation of the derived class:


/* Опережающее объявление структуры */
struct ClassB;
/* Так же, как и в предыдущем примере, храним указатели на методы класса */
typedef struct {
    void (*ClassB)(struct ClassB*);
    void (*set)(struct ClassB*);
    void (*get)(struct ClassA*);
} ClassB_functiontable;
typedef struct ClassB {
    ClassA inherited_class;
} ClassB;
void ClassB_constructor(ClassB *this);
void ClassB_set(ClassB *this);
int ClassB_get(ClassB *this);
ClassB_functiontable ClassB_vtable = {ClassB_constructor, ClassB_set, ClassB_get};
void ClassB_constructor(ClassB *this) {
    /* Требуется явное приведение типов */
    ClassA_constructor((ClassA*)this);
    /* Для таблицы виртуальных функций также требуется явное приведение типов */
    this->inherited_class.vtable = (ClassA_functiontable*)&ClassB_vtable;
}
void ClassB_set(ClassB *this) {
    printf("ClassB decreasing\n");
    this->inherited_class.data--;
}
int ClassB_get(ClassB *this) {
    this->inherited_class.vtable->set((ClassA*)this);
    return this->inherited_class.data;
}

As you can see from the code, we also call the function set()from the get()ClassB implementation using vtable, which points to the desired function set(), and also access the same integer datathrough the “inherited” class ClassA.

This is what the function looks like main():

int main() {
    ClassA classA;
    ClassB classB;
    ClassA_constructor(&classA);
    ClassB_constructor(&classB);
    printf("ClassA value: %d\n", classA.vtable->get(&classA));
    /* Обращаемся к get() через класс-предок */
    printf("ClassB value: %d\n",
               classB.inherited_class.vtable->get((struct ClassA*)&classB));
}

Conclusion:

ClassA is increasing
ClassA value: 11
ClassB decreasing
ClassB value: 9

Of course, this trick does not look as natural as in C ++ or in another object-oriented programming language, and I never had to implement something similar when I wrote programs in C, but nevertheless it can help to better understand the internal structure virtual functions.

from the translator: a detailed article about the internal implementation of virtual functions in C ++

Also popular now: