Понимание вывода, когда виртуальные функции вызываются напрямую с помощью vptr

Я просматривал полученный откуда-то код, чтобы понять, как работают 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 производного класса.


person cbinder    schedule 30.04.2014    source источник
comment
Механизм виртуального вызова определяется реализацией.   -  person Dark Falcon    schedule 30.04.2014
comment
Не обращая внимания на поведение undefined, это будет зависеть от компилятора. Прочтите документацию и/или исходный код вашего компилятора, чтобы узнать, каким он должен быть.   -  person molbdnilo    schedule 30.04.2014


Ответы (2)


Вся идея объектно-ориентированного программирования заключается в том, чтобы следовать абстракции. Если у вас есть виртуальная функция, предназначенная для динамического связывания, используйте ее через абстракцию, вызвав виртуальную функцию для указателя базового класса. В зависимости от того, куда он указывает динамически в иерархии классов, он будет динамически связываться. Зачем вам углубляться, чтобы сломать абстракцию и использовать vtbl и т. Д. (Где реализация варьируется от компилятора к компилятору на основе некоторых рекомендаций). Поэтому предложение состоит в том, чтобы использовать динамическое связывание в хорошем настроении.

person Dr. Debasish Jana    schedule 30.04.2014

Похоже, что новые виртуальные функции в Derive добавляются в конец vtable первого базового класса, а не помещаются в отдельную таблицу. В этом есть смысл: эффективнее расширять существующую таблицу, чем добавлять дополнительную, которая раздует каждый объект дополнительным указателем для каждого уровня наследования.

Все это, конечно, зависит от реализации. Язык не определяет, как должна быть реализована виртуальная диспетчеризация, а только то, как она должна работать при обычном использовании.

person Mike Seymour    schedule 30.04.2014