C++ / [Перевод] Виртуальные функции в C

в 7:56, , рубрики: велосипед, виртуальные функции

Недавно мне задали вопрос: как бы я реализовал механизм виртуальных функций на языке C?

Поначалу я понятия не имел, как это можно сделать: ведь C не является языком объектно-ориентированного программирования, и здесь нет такого понятия, как наследование. Но поскольку у меня уже было немного опыта с C, и я знал, как работают виртуальные функции, я подумал, что должен быть способ сымитировать поведение виртуальных функций, используя структуры (struct).

Краткое пояснение для тех, кто не знает, что такое виртуальные функции:
Виртуальная функция — это функция, которая может быть переопределена классом-наследником, для того чтобы тот имел свою, отличающуюся, реализацию. В языке C++ используется такой механизм, как таблица виртуальных функций
(кратко vtable) для того, чтобы поддерживать связывание на этапе выполнения программы. Виртуальная таблица — статический массив, который хранит для каждой виртуальной функции указатель на ближайшую в иерархии наследования реализацию этой функции. Ближайшая в иерархии реализация определяется во время выполнения посредством извлечения адреса функции из таблицы методов объекта.

Давайте теперь посмотрим на простой пример использования виртуальных функций в 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--;     } };

В приведенном примере у нас есть класс ClassA, имеющий два метода: get() и set(). Метод get() помечен как виртуальная функция; в классе ClassB его реализация меняется. Целое число data помечено ключевым словом protected, чтобы класс-наследник ClassB имел доступ к нему.

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

Вывод:

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

Теперь давайте подумаем, как реализовать концепцию виртуальных функций на C. Зная, что виртуальные функции представлены в виде указателей и хранятся в vtable, а vtable — статический массив, мы должны создать структуру, имитирующую сам класс ClassA, таблицу виртуальных функций для ClassA, а также реализацию методов ClassA.

/* Опережающее объявление структуры */ 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 increasingn");     this->data++; }  int ClassA_get(ClassA *this) {     this->vtable->set(this);     return this->data; } 

В C не существует указателя this, который указывал бы на вызывающий объект. Я назвал параметр та́к, для того чтобы сымитировать использование указателя this в C++ (кроме того это похоже на то, как на самом деле работает вызов метода объекта в C++).

Как мы видим из кода, приведенного выше, реализация ClassA_get() вызывает функцию set() через указатель из vtable. Теперь посмотрим на реализацию класса-наследника:

/* Опережающее объявление структуры */ 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 decreasingn");     this->inherited_class.data--; }  int ClassB_get(ClassB *this) {     this->inherited_class.vtable->set((ClassA*)this);     return this->inherited_class.data; } 

Как видно из кода, мы так же вызываем функцию set() из реализации get() ClassB, используя vtable, указывающую на нужную функцию set(), а также обращаемся к тому же целому числу data через «унаследованный» класс ClassA.

Вот так выглядит функция main():

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

Вывод:

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

Конечно же, этот трюк не выглядит настолько же естественно, как в C++ или в другом объектно-ориентированном языке программирования, и мне никогда не приходилось реализовывать нечто подобное, когда я писал программы на C, но тем не менее это может помочь лучше понять внутреннее устройство виртуальных функций.

от переводчика: подробная статья про внутреннюю реализацию виртуальных функций в C++

Автор: kharvd

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js