Недавно мне задали вопрос: как бы я реализовал механизм виртуальных функций на языке C?
Поначалу я понятия не имел, как это можно сделать: ведь C не является языком объектно-ориентированного программирования, и здесь нет такого понятия, как наследование. Но поскольку у меня уже было немного опыта с C, и я знал, как работают виртуальные функции, я подумал, что должен быть способ сымитировать поведение виртуальных функций, используя структуры (struct).
Краткое пояснение для тех, кто не знает, что такое виртуальные функции:
Виртуальная функция — это функция, которая может быть переопределена классом-наследником, для того чтобы тот имел свою, отличающуюся, реализацию. В языке C++ используется такой механизм, как таблица виртуальных функций
(кратко vtable) для того, чтобы поддерживать связывание на этапе выполнения программы. Виртуальная таблица — статический массив, который хранит для каждой виртуальной функции указатель на ближайшую в иерархии наследования реализацию этой функции. Ближайшая в иерархии реализация определяется во время выполнения посредством извлечения адреса функции из таблицы методов объекта.
Давайте теперь посмотрим на простой пример использования виртуальных функций в C++
В приведенном примере у нас есть класс
Вывод:
Теперь давайте подумаем, как реализовать концепцию виртуальных функций на C. Зная, что виртуальные функции представлены в виде указателей и хранятся в vtable, а vtable — статический массив, мы должны создать структуру, имитирующую сам класс ClassA, таблицу виртуальных функций для ClassA, а также реализацию методов ClassA.
В C не существует указателя
Как мы видим из кода, приведенного выше, реализация
Как видно из кода, мы так же вызываем функцию
Вот так выглядит функция
Вывод:
Конечно же, этот трюк не выглядит настолько же естественно, как в C++ или в другом объектно-ориентированном языке программирования, и мне никогда не приходилось реализовывать нечто подобное, когда я писал программы на C, но тем не менее это может помочь лучше понять внутреннее устройство виртуальных функций.
от переводчика: подробная статья про внутреннюю реализацию виртуальных функций в 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 increasing\n");
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 decreasing\n");
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: %d\n", classA.vtable->get(&classA));
/* Обращаемся к get() через класс-предок */
printf("ClassB value: %d\n",
classB.inherited_class.vtable->get((struct ClassA*)&classB));
}
Вывод:
ClassA is increasing
ClassA value: 11
ClassB decreasing
ClassB value: 9
Конечно же, этот трюк не выглядит настолько же естественно, как в C++ или в другом объектно-ориентированном языке программирования, и мне никогда не приходилось реализовывать нечто подобное, когда я писал программы на C, но тем не менее это может помочь лучше понять внутреннее устройство виртуальных функций.
от переводчика: подробная статья про внутреннюю реализацию виртуальных функций в C++