虚函数表和虚函数指针

  • 虚函数的地址存放于虚函数表中。运行期多态就是通过虚函数和虚函数表实现的。
    类的对象内部会有指向类内部的虚表地址的指针(每个类用了一个虚表,每个类的对象用了一个虚指针)。
    通过这个指针调用虚函数。虚函数的调用会被编译器转换为对虚函数表的访问。

  • ptr->f(); //ptr代表this指针,f是虚函数
    *(ptr->vptr[1])(ptr);
    ptr代表一个this指针,ptr指向的vptr是类内部的虚表指针。这个虚表指针会被放在类的最前方,
    1就是虚函数指针在虚函数表中的索引值。在这个索引值表示的虚表的槽中存放的就是f()的地址。

  • 虚表指针的名字也会被编译器更改,所以在多继承的情况下,类的内部可能存在多个虚表指针。通过不同的
    名字被编译器标识。

  • 虚函数表中可能还存在其他的内容,如用于RTTI的type_info类型。或者直接将虚基类的指针放在虚表中。
    压制多态可以通过域操作符进行

  • 单继承
    这种情况下,派生类中仅有一个虚函数表。这个虚函数表和基类的虚函数表不是一个表(无论派生类有没有
    重写基类的虚函数),但是如果派生类没有重写基类的虚函数的话,基类和派生类的虚函数表指向的函数地址都是
    相同的。

例子

class A
{
public:
	A(int _a1 = 1) : a(_a1) { }
	virtual void f() { cout << "A1::f" << endl; }
	virtual void g() { cout << "A1::g" << endl; }
	~A() {}
private:
	int a;
};
class B : public A
{
public:
	B(int _a1 = 1, int _c = 4) :A(_a1), b(_c) { }
	virtual void g() { cout << "C::g" << endl; }
private:
	int b;
};

*因为A有virtual void f(),和g(),所以编译器为A类准备了一个虚表vtableA,内容如下:
A::f的地址
A::g的地址
B因为继承了A,所以编译器也为B准备了一个虚表VtableB,内容如下:
A::f的地址
B::g的地址
因为B::g重写了,所以B的虚表的g放的是B::g的入口地址,但是f是从上面的A继承下来的,所以
f的地址是A::f的入口地址。
在构造B的对象的时候即(B b),编译器分配内存空间,除了classA的int a,B的成员int b;以外,还分配了一个虚指针vptr,
指向B的虚表vtableB,b的布局如下:
vptr:指向B的虚表vtableB
int a:继承A的成员
int b:B成员
A pa =&b;
pa的结构就是A的布局(就是说用pa只能访问到b对象的前两项,访问不到第三项int b)
pa->g()中,编译器知道的是,g是一个声明为virtual的成员函数,而且其入口地址放在表格的第2项,那么编译器
编译这条语句的时候就如是转换(pa->vptr)[1]。这一项放的是B::g()的入口地址,则就实现了多态

你可能感兴趣的:(杂)