Компиляторам разрешено реализовывать это по своему усмотрению. Но они обычно следуют старой реализации CFront.
Для классов / объектов без наследования
Рассмотрим:
#include <iostream>
class A {
void foo()
{
std::cout << "foo\n";
}
static int bar()
{
return 42;
}
};
A a;
a.foo();
A::bar();
Компилятор изменяет эти последние три строки во что-то похожее на:
struct A a = <compiler-generated constructor>;
A_foo(a); // the "a" parameter is the "this" pointer, there are not objects as far as
// assembly code is concerned, instead member functions (i.e., methods) are
// simply functions that take a hidden this pointer
A_bar(); // since bar() is static, there is no need to pass the this pointer
Когда-то давно я догадывался, что это обрабатывается указателями на функции в каждом созданном A
объекте. Однако такой подход будет означать, что каждый объект A
будет содержать одинаковую информацию (указатель на одну и ту же функцию), которая будет занимать много места. Компилятору достаточно просто позаботиться об этих деталях.
Для классов / объектов с не виртуальным наследованием
Конечно, это не совсем то, что вы просили. Но мы можем распространить это на наследование, и это то, что вы ожидаете:
class B : public A {
void blarg()
{
// who knows, something goes here
}
int bar()
{
return 5;
}
};
B b;
b.blarg();
b.foo();
b.bar();
Компилятор превращает последние четыре строки в нечто вроде:
struct B b = <compiler-generated constructor>
B_blarg(b);
A_foo(b.A_portion_of_object);
B_bar(b);
Примечания о виртуальных методах
Все становится немного сложнее, когда вы говорите о virtual
методах. В этом случае каждый класс получает специфичный для класса массив указателей на функции, по одному такому указателю на каждую virtual
функцию. Этот массив называется виртуальной таблицей («виртуальная таблица»), и каждый созданный объект имеет указатель на соответствующую виртуальную таблицу. Вызовы virtual
функций разрешаются путем поиска правильной функции для вызова в vtable.