Имя пользователя:
Пароль:  
Помощь | Регистрация | Забыли пароль?  

Показать сообщение отдельно
pva pva вне форума

Аватара для pva

Ветеран


Сообщения: 1180
Благодарности: 279

Профиль | Отправить PM | Цитировать


Обрисую как это устроено. Любой полиморфный класс (то есть содержащий виртуальные функции) содержит внутреннюю переменную - указатель на таблицу виртуальных функций.
Код: Выделить весь код
class E
{
private: virtual void mem_fun()
  {
     // do something
  }
public: virtual bool L(int)
  {
     // do something
  }
}

// кодируется как (условный код):
struct E_vtbl
{
   int base_offset = 0;
   mem_fun_address = &mem_fun; // адрес кода mem_fun
   L_address = &L; // адрес кода L
}

struct E_class
{
    E_vtbl* _vtbl = &E_vtbl;
}

// таким образом вызов виртуальной функции идёт так:
// p->mem_fun();
(*p->_vtbl->mem_fun_address)(p + p->_vtbl->base_offset);

// p->L(1);
(*p->_vtbl->L_address)(p + p->_vtbl->base_offset, 1);
У чистых виртуальных функций считается что указатель в виртуальной таблице является нулём (оттуда и запись), но на самом деле бывает что такой таблицы вообще нет, а бывает, что там находится код, который выдаёт сообщение об ошибке "pure virtual function called". Таким образом:
Код: Выделить весь код
class E
{
int a;
public: virtual bool L(int) = 0;
}

class E1 : public E
{
int b;
public: virtual bool L(int)
  {
     cout << "E1::L\n";
  }
}

class E2 : public E
{
double c;
public: virtual bool L(int)
  {
     cout << "E2::L\n";
  }
}

// кодируется как:
struct E1_vtbl
{
   base_offset = 0;
   L_address = &E1::L // E_vtbl, заполненная правильным адресом E1::L
}

struct E1_vtbl_E
{
   base_offset = -4;
   L_address = &E1::L // E_vtbl, заполненная правильным адресом E1::L
}

struct E2_vtbl
{
   base_offset = 0;
   L_address = &E2::L // E_vtbl, заполненная правильным адресом E2::L
}

struct E2_vtbl_E
{
   base_offset = -4;
   L_address = &E2::L // E_vtbl, заполненная правильным адресом E2::L
}

struct E1_class
{
// общие для всех
    E1_vtbl* _vtbl = &E1_vtbl; // таблица E1
// составляющий класс E
    E_vtbl* _vtbl1 = &E1_vtbl_E; // таблица E
    int a; // данные E

    int b; // общие данные
}

struct E2_class
{
// общие для всех
    E2_vtbl* _vtbl = &E2_vtbl; // таблица E2
// составляющий класс E
    E_vtbl* _vtbl1 = &E2_vtbl_E; // таблица E
    int a; // данные E

    double c; // общие данные
}
1. преобразование указателя к E - вычисление адреса, где находится составляющий класс: E_ptr = E2_ptr + 4;
2. вызов E::L - из виртуальной таблицы (*E_ptr->_vtbl->L)(E_ptr+E_ptr->_vtbl->base_offset, 1). В результате, если посчитать, вызов преобразуется в E2_ptr->E::L2
Для чего нужен base_offset? Дело в том, что в C++ классы обладают множественным виртуальным наследием. Чтобы программа могла при реализации вызова могла преобразовать адрес к наследнику от любого базового класса (который может и не "знать", в состав чего входит). Иногда base_offset вводят в тело функции. Идёт вызов кода, который вносит изменения в указатель this и делает прыжок на функцию класса-наследника
Это сообщение посчитали полезным следующие участники:

Отправлено: 08:26, 07-11-2008 | #5