Ответ 1
Классы (более или менее) построены как регулярные структуры. Методы (более или менее...) преобразуются в функции, первый параметр которых равен "this". Ссылки на переменные класса выполняются как смещение на "this".
Что касается наследования, процитируйте цитату из С++ FAQ LITE, которая здесь отражена http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.4. В этой главе показано, как виртуальные функции вызывают в реальном оборудовании (что делает компилятор в машинный код.
Пусть работает пример. Предположим, что класс Base имеет 5 виртуальных функций: virt0()
через virt4()
.
// Your original C++ source code
class Base {
public:
virtual arbitrary_return_type virt0(...arbitrary params...);
virtual arbitrary_return_type virt1(...arbitrary params...);
virtual arbitrary_return_type virt2(...arbitrary params...);
virtual arbitrary_return_type virt3(...arbitrary params...);
virtual arbitrary_return_type virt4(...arbitrary params...);
...
};
Шаг # 1: компилятор создает статическую таблицу, содержащую 5 указателей функций, где-то похоронив эту таблицу в статическую память. Многие (не все) компиляторы определяют эту таблицу при компиляции .cpp, которая определяет базовую первую не встроенную виртуальную функцию. Мы называем эту таблицу v-таблицей; допустим, что его техническое имя Base::__vtable
. Если указатель функции вписывается в одно машинное слово на целевой аппаратной платформе, Base::__vtable
в конечном итоге потребляет 5 скрытых слов памяти. Не 5 на экземпляр, а не 5 на каждую функцию; просто 5. Это может выглядеть примерно так: псевдокод:
// Pseudo-code (not C++, not C) for a static table defined within file Base.cpp
// Pretend FunctionPtr is a generic pointer to a generic member function
// (Remember: this is pseudo-code, not C++ code)
FunctionPtr Base::__vtable[5] = {
&Base::virt0, &Base::virt1, &Base::virt2, &Base::virt3, &Base::virt4
};
Шаг # 2: компилятор добавляет скрытый указатель (обычно также машинное слово) к каждому объекту класса Base. Это называется v-указателем. Подумайте об этом скрытом указателе как о скрытом элементе данных, как будто компилятор переписывает ваш класс примерно так:
// Your original C++ source code
class Base {
public:
...
FunctionPtr* __vptr; ← supplied by the compiler, hidden from the programmer
...
};
Шаг # 3: компилятор инициализирует this->__vptr
внутри каждого конструктора. Идея состоит в том, чтобы каждый объект v-указатель указывал на свой класс v-table, как если бы он добавлял следующую инструкцию в каждый init-list конструктора:
Base::Base(...arbitrary params...)
: __vptr(&Base::__vtable[0]) ← supplied by the compiler, hidden from the programmer
...
{
...
}
Теперь давайте обработаем производный класс. Предположим, что ваш код на С++ определяет класс Der, который наследуется от класса Base. Компилятор повторяет шаги №1 и №3 (но не # 2). На шаге 1 компилятор создает скрытую v-таблицу, сохраняя те же функции-указатели, что и в Base::__vtable
, но заменяя те слоты, которые соответствуют переопределениям. Например, если Der переопределяет virt0()
через virt2()
и наследует другие as-is, Der v-table может выглядеть примерно так (притвориться, что Der не добавляет никаких новых виртуальных машин):
// Pseudo-code (not C++, not C) for a static table defined within file Der.cpp
// Pretend FunctionPtr is a generic pointer to a generic member function
// (Remember: this is pseudo-code, not C++ code)
FunctionPtr Der::__vtable[5] = {
&Der::virt0, &Der::virt1, &Der::virt2, &Base::virt3, &Base::virt4
}; ^^^^----------^^^^---inherited as-is
На шаге 3 компилятор добавляет аналогичное назначение указателя в начале каждого из конструкторов Der. Идея состоит в том, чтобы изменить каждый v-указатель объекта Der, чтобы он указывал на свой класс v-table. (Это не второй v-указатель, тот же v-указатель, который был определен в базовом классе Base, помните, что компилятор не повторяет шаг # 2 в классе Der.)
Наконец, посмотрим, как компилятор реализует вызов виртуальной функции. Ваш код может выглядеть так:
// Your original C++ code
void mycode(Base* p)
{
p->virt3();
}
Компилятор не знает, будет ли это вызов Base::virt3()
или Der::virt3()
или, возможно, метод virt3()
другого производного класса, который еще не существует. Он точно знает, что вы вызываете virt3()
, который является функцией в слоте № 3 v-таблицы. Он переписывает этот вызов: *//
// Pseudo-code that the compiler generates from your C++
void mycode(Base* p)
{
p->__vptr[3](p);
}
Я настоятельно рекомендую всем разработчикам С++ прочитать FAQ. Это может занять несколько недель (так как трудно читать и долго), но это научит вас многому о С++ и что с ним можно сделать.