Указатели виртуальных таблиц

Я решил выяснить, как реально создается vtable. Итак, я открыл отладчик и обнаружил некоторые странные вещи. Узел PTR содержит несколько вптр. Я всегда думал, что был только один vptr на объект. Кто-нибудь может мне объяснить, что здесь происходит? (Я имею в виду, когда указатель базового класса указывает на объект производного класса)

#include <iostream>
using namespace std;

class Base
{
int base;
public:
virtual void say()
{
cout << "Hello" << endl;
}
virtual void no()
{
cout << "No" << endl;
}
};class Base2
{
public:
virtual void lol()
{
cout << "lol" << endl;
}
};

class Derv:public Base,public Base2
{
public:
void say()
{
cout << "yep" << endl;
}

};int main()
{

Base* ptr = new Derv();
ptr->say();
ptr = new Base();
ptr->say();
}

http://s018.radikal.ru/i504/1405/1e/38832e978dd5.jpg

2

Решение

Два указателя необходимы, потому что у вас есть два базовых класса с виртуальными функциями.

Давайте пройдемся по шагам:

Вы сначала определяете Base который имеет виртуальные функции. Поэтому компилятор создаст виртуальную таблицу, которая примерно выглядит следующим образом (индексы приведены в скобках; обратите внимание, что это пример, точная компоновка таблицы будет зависеть от компилятора):

[0] address of Base::say()
[1] address of Base::no()

в Base макет будет поле __vptr (или, тем не менее, он называется, если он вообще указан), указывая на эту таблицу. Когда дан указатель pBase типа Base* и попросил позвонить say, компилятор на самом деле вызовет (p->__vptr[0])(),

Далее вы определяете второй, независимый класс Base2, чья виртуальная таблица будет выглядеть так:

[0] address of Base2::lol()

Вызов lol через Base2 указатель теперь будет переведен на что-то вроде (pBase2->__vptr[0])(),

Теперь, наконец, вы определяете класс Derv который наследует от обоих Base а также Base2, Это особенно означает, что вы можете иметь как Base* и Base2* указывая на объект типа Derv, Теперь, если бы у вас был только один __vptr, pBase->say() а также pBase2->lol() будет вызывать одну и ту же функцию, потому что они оба переводят в (pXXX->__vptr[0])(),

Однако на самом деле происходит то, что есть два __vptr поля, одно для Base базовый класс, и один для _Base2 Базовый класс. Base* указывает на Base подобъект со своим __vptr, Base2* указывает на Base2 подобъект со своим __vptr, Теперь Derv виртуальный стол может выглядеть, например, как это:

[0] address of Derv::say()
[1] address of Base::no()
[2] address of Base2::lol()

__vptr из Base подобъект указывает на начало этой таблицы, а __vptr из Base2 подобъект указывает на элемент [2], Сейчас звоню pBase->say() будет переводить на (pBase->__vptr[0])()и так как __vptr из Base подобъект указывает на начало Dervвиртуальная таблица, она в конечном итоге вызывает Derv::say() как предполагалось. С другой стороны, если вы звоните pBase2->lol() оно будет переведено на (pBase2->__vptr[0])(), но с тех пор pBase2 указывает на Base2 подобъект od Dervбудет разыменовано соответствующее __vptr который указывает на элемент [2] из Dervвиртуальная таблица, где адрес Base2::lol хранится. А сейчас Base2::lol() называется как задумано.

4

Другие решения

Подумайте о том, что происходит, когда вы приводите указатель к производному указателю на базу, он должен ссылаться на блок памяти, имеющий ту же структуру, что и базовый тип. Когда у вас есть множественное наследование, вы получите один vptr в каждой базе, которая имеет виртуальные функции.

1

По вопросам рекламы [email protected]