Я просматривал код, который получил откуда-то, чтобы понять, как работают vptr и vtable. Ниже приведен код с выводом
class Base1
{
virtual void fun1() { cout<< "Base1::fun1()" << endl; }
virtual void func1() { cout<< "Base1::func1()" << endl; }
};
class Base2 {
virtual void fun1() { cout<< "Base2::fun1()" << endl; }
virtual void func1() { cout<< "Base2::func1()" << endl; }
};
class Base3 {
virtual void fun1() { cout<< "Base3::fun1()" << endl; }
virtual void func1() { cout<< "Base3::func1()" << endl; }
};
class Derive : public Base1, public Base2, public Base3
{
public:
virtual void Fn()
{
cout<< "Derive::Fn" << endl;
}
virtual void Fnc()
{
cout<< "Derive::Fnc" << endl;
}
};
typedef void(*Fun)(void);
int main()
{
Derive obj;
Fun pFun = NULL;
// calling 1st virtual function of Base1
pFun = (Fun)*((int*)*(int*)((int*)&obj+0)+0);
pFun();
// calling 2nd virtual function of Base1
pFun = (Fun)*((int*)*(int*)((int*)&obj+0)+1);
pFun();
// calling 1st virtual function of Base2
pFun = (Fun)*((int*)*(int*)((int*)&obj+1)+0);
pFun();
// calling 2nd virtual function of Base2
pFun = (Fun)*((int*)*(int*)((int*)&obj+1)+1);
pFun();
// calling 1st virtual function of Base3
pFun = (Fun)*((int*)*(int*)((int*)&obj+2)+0);
pFun();
// calling 2nd virtual function of Base3
pFun = (Fun)*((int*)*(int*)((int*)&obj+2)+1);
pFun();
// calling 1st virtual function of Derive
pFun = (Fun)*((int*)*(int*)((int*)&obj+0)+2);
pFun();
// calling 2nd virtual function of Derive
pFun = (Fun)*((int*)*(int*)((int*)&obj+0)+3);
pFun();
return 0;
}
OUTPUT:
Base1::fun
Base1::func
Base2::fun
Base2::func
Base3::fun
Base3::func
Derive::Fn
Derive::Fnc
Это выглядит хорошо, но способ вызова виртуальных функций производного класса не понят. Не должно ли быть так:
// calling 1st virtual function of Derive
pFun = (Fun)*((int*)*(int*)((int*)&obj+3)+0);
pFun();
// calling 2nd virtual function of Derive
pFun = (Fun)*((int*)*(int*)((int*)&obj+3)+1);
это был адрес виртуальных функций, доступ к которым осуществляется с помощью vptr производного класса, который в конечном итоге указывает на vtable производного класса.
Вся идея объектно-ориентированного программирования — следовать абстракции. Когда у вас есть виртуальная функция, предназначенная для динамического связывания, используйте ее через абстракцию, вызывая виртуальную функцию для указателя базового класса. В зависимости от того, куда он динамически указывает в иерархии классов, он будет динамически связываться. Зачем вам идти глубже, чтобы нарушить абстракцию и использовать vtbl и т. Д. (Где реализация варьируется от компилятора к компилятору на основании некоторого руководства). Поэтому мы предлагаем использовать динамическое связывание в хорошем духе.
Похоже, новые виртуальные функции в Derive
добавляются в конец vtable первого базового класса, а не помещаются в отдельную таблицу. Это имеет смысл: более эффективно расширять существующую таблицу, чем добавлять дополнительную, которая бы раздувала каждый объект с дополнительным указателем для каждого уровня наследования.
Все это, конечно, зависит от реализации. Язык не определяет, как должна быть реализована виртуальная диспетчеризация, только как она должна работать при обычном использовании.