Pull to refresh

Виртуальные функции в C

Reading time 4 min
Views 29K
Original author: Milot Shala
Недавно мне задали вопрос: как бы я реализовал механизм виртуальных функций на языке 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++
Tags:
Hubs:
+22
Comments 54
Comments Comments 54

Articles