Я использую GCC на Linux.
Я хочу понять рабочие Виртуальные функции.
Какой код C ++ я должен написать, чтобы увидеть и понять, как именно статическое и динамическое связывание происходит с и без Виртуальных функций?
И как «увидеть», как они были окончательно связаны и что именно произошло во время процесса?
Вот пример. Вы можете создать и запустить этот код с функцией, установленной как виртуальная функция или нет. Чтобы получить виртуальное поведение, динамическую диспетчеризацию, динамическое связывание, создайте его с помощью макроса препроцессора. IS_VIRTUAL
определены. Чтобы увидеть статическое связывание, создайте его без определения этого макроса.
#include <iostream>
#if defined(IS_VIRTUAL)
#define CONDITIONAL_VIRTUAL virtual
#else
#define CONDITIONAL_VIRTUAL
#endif
struct A {
CONDITIONAL_VIRTUAL void foo() { std::cout << "A\n"; }
};
struct B : A {
CONDITIONAL_VIRTUAL void foo() { std::cout << "B\n"; }
};
// global objects
A a; B b;
enum object_type { get_A, get_B };
A *get_object(object_type t) {
switch (t) {
case get_A: return &a;
case get_B: return &b;
}
}
int main() {
std::cout << "Choose A or B: ";
char c;
std::cin >> c;
A *x = get_object( c == 'A' ? get_A : get_B );
x->foo();
}
Привязка имеет отношение к оценке x->foo()
, Компилятор должен выяснить, какой код выполнять для этого выражения. При статическом и динамическом связывании компилятор x
и видит его тип A*
так это выглядит на struct A
и ищет foo()
декларация.
При статической привязке компилятор находит, что foo()
не является virtual
так что компилятор просто идет вперед и генерирует код, который вызывает foo()
метод. Просто.
При динамическом связывании компилятор видит этот метод как virtual
и поэтому компилятор вместо этого генерирует код, который во время выполнения будет использовать таблицу указателей функций, связанных с объектом x
выбрать метод для вызова, а затем вызвать любой метод найден. Компилятор также генерирует код в другом месте для создания таблиц для глобального a
а также b
объекты. Для глобального a
возражает, что таблица указывает на A::foo()
и для глобального b
это заставляет таблицу указывать на B::foo()
, Так что если x
указывает на b
объект, то поиск таблицы приведет к B::foo()
и это функция, которая будет вызвана.
В общем, компилятор должен убедиться, что все объекты, которые имеют виртуальные методы, также имеют таблицу с ними, которая указывает на нужные функции для вызова, так что каждый раз, когда виртуальный вызов выполняется для объекта, программа может во время выполнения получить таблицу, связанную с объектом, и найдите правильный метод для вызова.
Так что соберите вышеупомянутую программу как в статическом, так и в динамическом режимах, а затем запустите ее и посмотрите результат, который вы получаете для каждого входа. Заполните таблицу ниже данными, которые вы получаете для каждой комбинации ввода и типа привязки.
Binding | static dynamic
Input
-----
A ? ?
B ? ?
Во всех случаях выход производится путем оценки того же x->foo()
вызов метода. В каких случаях динамическое связывание доказательств? Соответствует ли это вашему пониманию приведенного выше объяснения динамического связывания?
class Base {
public:
int Foo();
virtual int Bar();
};
class D1 : public Base {
public:
int Foo();
virtual int Bar();
};
class D2 : public Base {
public:
int Foo();
virtual int Bar();
};
main()
{
Base * b = (rand() < 100) ? new D1 : new D2;
// Always calls Base::Foo()
b->Foo();
// Call either D1::Bar() or D2::Bar()
b->Bar();
}