实现机制:虚函数通过虚函数表来实现。虚函数的地址保存在虚函数表中,在类的对象所在的内存空间中,保存了指向虚函数表的指针(称为“虚表指针”),通过虚表指针可以找到类对应的虚函数表。虚函数表解决了基类和派生类的继承问题和类中成员函数的覆盖问题,当用基类的指针来操作一个派生类的时候,这张虚函数表就指明了实际应该调用的函数。
虚函数表相关知识点:
实例:
无虚函数覆盖的情况:
C++
#include <iostream>
using namespace std;
class Base
{
public:
virtual void B_fun1() { cout << "Base::B_fun1()" << endl; }
virtual void B_fun2() { cout << "Base::B_fun2()" << endl; }
virtual void B_fun3() { cout << "Base::B_fun3()" << endl; }
};
class Derive : public Base
{
public:
virtual void D_fun1() { cout << "Derive::D_fun1()" << endl; }
virtual void D_fun2() { cout << "Derive::D_fun2()" << endl; }
virtual void D_fun3() { cout << "Derive::D_fun3()" << endl; }
};
int main()
{
Base *p = new Derive();
p->B_fun1(); // Base::B_fun1()
return 0;
}
主函数中基类的指针 p 指向了派生类的对象,当调用函数 B_fun1() 时,通过派生类的虚函数表找到该函数的地址,从而完成调用。
带有虚函数的类,通过该类所隐含的虚函数表来实现多态机制,该类的每个对象均具有一个指向本类虚函数表的指针,这一点并非 C++ 标准所要求的,而是编译器所采用的内部处理方式。实际应用场景下,不同平台、不同编译器厂商所生成的虚表指针在内存中的布局是不同的,有些将虚表指针置于对象内存中的开头处,有些则置于结尾处。如果涉及多重继承和虚继承,情况还将更加复杂。因此永远不要使用 C 语言的方式调用 memcpy() 之类的函数复制对象,而应该使用初始化(构造和拷构)或赋值的方式来复制对象。
程序示例,我们通过对象内存的开头处取出 vptr,并遍历对象虚函数表。
C++
#include <iostream>
#include <memory>
using namespace std;
typedef void (*func)(void);
class A {
public:
void f() { cout << "A::f" << endl; }
void g() { cout << "A::g" << endl; }
void h() { cout << "A::h" << endl; }
};
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
class Derive: public Base {
public:
void f() { cout << "Derive::f" << endl; }
void g() { cout << "Derive::g" << endl; }
void h() { cout << "Derive::h" << endl; }
};
int main()
{
Base base;
Derive derive;
//获取vptr的地址,运行在gcc x64环境下,所以将指针按unsigned long *大小处理
//另外基于C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置
unsigned long* vPtr = (unsigned long*)(&base);
//获取vTable 首个函数的地址
func vTable_f = (func)*(unsigned long*)(*vPtr);
//获取vTable 第二个函数的地址
func vTable_g = (func)*((unsigned long*)(*vPtr) + 1);//加1 ,按步进计算
func vTable_h = (func)*((unsigned long*)(*vPtr) + 2);//同上
vTable_f();
vTable_g();
vTable_h();
vPtr = (unsigned long*)(&derive);
//获取vTable 首个函数的地址
vTable_f = (func)*(unsigned long*)(*vPtr);
//获取vTable 第二个函数的地址
vTable_g = (func)*((unsigned long*)(*vPtr) + 1);//加1 ,按步进计算
vTable_h = (func)*((unsigned long*)(*vPtr) + 2);//同上
vTable_f();
vTable_g();
vTable_h();
cout<<sizeof(A)<<endl;
cout<<sizeof(base)<<endl;
cout<<sizeof(derive)<<endl;
return 0;
}
/*
Base::f
Base::g
Base::h
Derive::f
Derive::g
Derive::h
1
8
8
*/
我们可以看到同样的函数实现,对象在分配空间时,编译器会为对象多分配一个 vptr 指针的空间。
3.虚函数的使用场景:
阅读量:2026
点赞量:0
收藏量:0