c++虚函数和虚函数表原理

首先我们要知道函数是有地址的,虚函数也不例外。虚函数的地址存放在虚函数表中,运行期

多态就是通过虚函数和虚函数表来实现的。类的对象内部会有指向类内部虚表的指针,通过这个

指针调用虚函数,虚函数的调用会被编译器转换为对虚表的访问。虚表指针名字也会被编译器修改,所以在多继承下,类的内部可能存在多个虚表指针。

==========华丽的分割线==============

虚函数提供了动态多态,但它也是可以被压制,下面来看下例子。

class A

{

public:

virtual void f(){ std::cout<

};

classB : public A

{

public:

virtual void f(){std::cout<

};

int main()

{

B b ;

b.f(); //因为B重载了f的实现 所以会调用B的f

//那么怎么才能调用到基类的f呢 我们可以通过域操作符来进行

b.A::f();

}

==========华丽的分割线==============

单继承的话派生类中仅有一个虚函数表。这个虚函数表和基类的虚函数表不是一个表。下面来看例子。

class A1

{

public:

A1(int _a1 = 1):a1(_a1){}

virtual void f() {std::cout<

virtual void g() {std::cout<

private:

int a1;

};

class B1:public A1

{

public:

B1(int _a1 = 1, int _b1 = 4):A1(_a1),b(_b1){}

private:

int b;

};

int main()

{

B1 b1;

b1.g();

return 0;

}

如果派生类没有重写基类的虚函数那么派生类中虚函数表的虚函数地址和基类是一样的。


c++虚函数和虚函数表原理

==========华丽的分割线==============

多继承情况下,派生类中有多个虚函数表,虚函数表的排列方式和继承的顺序一致,派生类重写函数将会

覆盖所有虚函数表的同名内容,派生类自定义的新虚函数将会在第一个类的虚函数表的后面扩充

下面看例子:

class A1

{

public:

A1(int _a1 = 1) : a1(_a1) { }

virtual void f() { cout << "A1::f" << endl; }

virtual void g() { cout << "A1::g" << endl; }

virtual void h() { cout << "A1::h" << endl; }

~A1() {}

private:

int a1;

};

class A2

{

public:

A2(int _a2 = 2) : a2(_a2) { }

virtual void f() { cout << "A2::f" << endl; }

virtual void g() { cout << "A2::g" << endl; }

virtual void h() { cout << "A2::h" << endl; }

~A2() {}

private:

int a2;

};

class A3

{

public:

A3(int _a3 = 3) : a3(_a3) { }

virtual void f() { cout << "A3::f" << endl; }

virtual void g() { cout << "A3::g" << endl; }

virtual void h() { cout << "A3::h" << endl; }

~A3() {}

private:

int a3;

};

class B : public A1, public A2, public A3

{

public:

B(int _a1 = 1, int _a2 = 2, int _a3 = 3, int _b = 4) :A1(_a1), A2(_a2), A3(_a3), b(_b) { }

virtual void f1() { cout << "B::f" << endl; }

virtual void g1() { cout << "B::g" << endl; }

virtual void h1() { cout << "B::h" << endl; }

private:

int b;

};


c++虚函数和虚函数表原理

==========华丽的分割线==============

如果多重继承的情况下,派生类重载了基类的虚函数,虚表是什么样子的呢?

下面我们上代码看看

class A1

{

public:

A1(int _a1 = 1) : a1(_a1) { }

virtual void f() { cout << "A1::f" << endl; }

virtual void g() { cout << "A1::g" << endl; }

virtual void h() { cout << "A1::h" << endl; }

~A1() {}

private:

int a1;

};

class A2

{

public:

A2(int _a2 = 2) : a2(_a2) { }

virtual void f() { cout << "A2::f" << endl; }

virtual void g() { cout << "A2::g" << endl; }

virtual void h() { cout << "A2::h" << endl; }

~A2() {}

private:

int a2;

};

class A3

{

public:

A3(int _a3 = 3) : a3(_a3) { }

virtual void f() { cout << "A3::f" << endl; }

virtual void g() { cout << "A3::g" << endl; }

virtual void h() { cout << "A3::h" << endl; }

~A3() {}

private:

int a3;

};

class B : public A1, public A2, public A3

{

public:

B(int _a1 = 1, int _a2 = 2, int _a3 = 3, int _b = 4) :A1(_a1), A2(_a2), A3(_a3), b(_b) { }

virtual void f() { cout << "B::f" << endl; }

virtual void g() { cout << "B::g" << endl; }

virtual void h() { cout << "B::h" << endl; }

private:

int b;

};


c++虚函数和虚函数表原理

从编译器给出的信息我们可以看到在第二个虚函数表中有adjustor{8}的字样,这就是A类的大小,也就是

说这就是告诉编译器需要进行8字字节的偏移。当B类用不同的基类指针指向的时候,运行的是不同的基类中的

虚函数,例如A2类指向B的时候虚函数指针是自动跳到B类中的A2类所在的地方的。经过这样的调整

A1 A2 A3都会指向正确的类的位置。当B类重写了函数之后 A2 A3的虚函数表所指向的已经不再是简单的函数指针了

而是一个trunk对象,这就是c++的trunk技术。所谓的trunk就是一段汇编代码 这段汇编代码可以适当的偏移来

调整this指针来跳到对应的虚函数中去 并调用这个函数,也就是当使用A1的指针指向B的对象的时候 不需要发生偏移

而使用A2指向B时则需要进行sizeof(A1)字节的偏移,并跳转到A2中的函数来执行,这就是通过trunk的jump

指令跳转到这个函数的。


分享到:


相關文章: