c++ 继承关系中的虚函数表

本文详细解析了C++中虚函数表的实现机制,包括子类如何继承父类虚函数、虚函数的声明与覆盖、虚函数表的内存分配、多重继承下的虚函数表组织、以及虚函数在构造和析构中的行为。重点讨论了动态绑定和虚函数表的使用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 子类继承父类的虚函数表的方式是复制一份。存在虚函数的类,都有自己的虚函数表,不与其他类共用。

2. 只要祖先类的某个函数被声明为virtual, 则在后代中无论是否显式地添加virtual,该函数一直都是虚的。

3. 如果子类重写了某个虚函数,则该类的虚函数表对应位置上的虚函数地址会被覆盖。如果完全不重写,则子类的虚函数表里面的内容和父类虚函数表内容保持一致。(需要注意的是,虽然父类的函数地址被覆盖了,但是默认的参数还是保留了,如果用父类指针去调用子类重载的函数,传的默认参数值还是父类给的默认值,只有用子类本身类型的指针去调用,才会传子类给的默认值)。

4. 虚函数表类似于类的静态成员,被该类的所有实例共享。只不过该表是只读的,不存在线程安全问题。

5. 实例对象在起始4字节中,会存放虚函数表的地址(跟系统寻址范围有关,有的系统8字节才能表示一个地址),可以通过手动寻址的方式完成虚函数调用。

6. 多重继承(有多个父类并列)时,该类有多张虚函数表,实例对象的0~3字节,表示第一个虚函数表地址,4~n字节表示第一个父类的成员变量;n+1~n+4表示第二张虚函数表地址,n+5~m表示第二个父类成员变量,以此类推(C++对象内存模型)。当这些父类又有共同祖先时,为避免数据冗余和冲突,继承方式应该是虚继承。

7. 用父类指针指向子类对象时,指针地址会根据多重继承顺序的发生相应的偏移。如A:B,C表示A同时继承了B和C,当用B*类型指向A类对象时,指针偏移0字节,当用C类型指向A类对象时,指针偏移sizeof(B)字节。用代码表示如下:

#ifdef __WIN64

typedef __int64 ADTY;

#else

typedef __int32 ADTY;

#endif

A obj;

A* a_ptr = &obj;

B* b_ptr = &obj;

C* c_ptr = &obj;

assert(((ADTY)c_ptr - (ADTY)b_ptr) == sizeof(B));

assert((ADTY)a_ptr == (ADTY)b_ptr);

8. 在构造一个对象时,对象的虚表指针指向的地址会随着构造过程发生变化。执行父类构造函数时,虚表指针指向了父类的虚表,执行子类构造函数时,虚表指针指向子类虚表。因此在构造函数中调用虚函数不会发生多态效应。

9. 析构函数不会被子类继承,在delete多态指针时,如果发现指针类型的析构函数为虚,编译器将会动用一系列复杂的机制,找到该指针真实的类型,进而调用真实的析构(该过程是静态绑定,不是运行时动态调整的)。因此,如果一个类可能会被继承,则必须将其析构函数定义为virtual。

10. 即便父类的虚函数是private的,子类的虚函数表中也依然存在它的地址,我们可以通过子类实例的首地址,寻找到虚函数表进而找到该虚函数,完成调用。同样,我们也可以通过手动寻址访问父类的私有成员变量。这样会违背c++语言的设计初衷,破坏数据的安全性,一般不要轻易使用。

#include <iostream>
#include <string>
using namespace std;

//声明virtual,让该成员函数地址在虚函数表中留下记录
class Brass {
private:
    std::string fullname;
    long acctNum;
    double balance;
public:
    Brass(const std::string& s = "NullBody", long an = -1, double bal = 0.0);
    void Deposit(double amt);
    virtual void ViewAcct() const;
    virtual void withdraw(double amt);
    double Balance() const;
    virtual void test() { cout << "Brass::test" << endl; }
    virtual ~Brass() {}
};

class BrassExtra
{
public:
    int vvk;
    BrassExtra() :vvk(97) {}
    virtual void SecondMethod()
    {
        cout << "BrassExtra SecondMethod" << endl;
    }
};

//即便子类不再将父类的虚函数函数声明为virtual,只要祖先类中该函数是virtual,则它依然是虚函数
class BrassPlus :public Brass,public BrassExtra {
private:
    double maxLoan;
    double rate;
    double owesBank;
public:
    BrassPlus(const std::string& s = "NullBody", long an = -1,
        double bal = 0.0, double ml = 500, double r = 0.11125);
    BrassPlus(const Brass& ba, double ml = 500, double r = 0.11125);
    void SecondMethod();
    void ViewAcct()const;
    void withdraw(double amt);
    void ResetMax(double m) { maxLoan = m; }
    void ResetRate(double r) { rate = r; }
    void ResetOwes() { owesBank = 0; }
};

class BrassPlusPlus :public BrassPlus {
public:
    void ViewAcct() const override { cout << "BrassPlusPlus viewacct" << endl; }
};


Brass::Brass(const std::string& s, long an, double bal) :fullname(s), acctNum(an), balance(bal) { cout << "fullname: " << fullname <<","<<"acctNum: "<< acctNum<<","<<"balance: "<< balance << endl; }
void Brass::Deposit(double amt) { return; }
void Brass::withdraw(double amt) { cout << "Brass withdraw: " << amt << endl; return; }
double Brass::Balance() const { return balance; }
void Brass::ViewAcct()const { cout << "Brass viewacct" << endl; }

BrassPlus::BrassPlus(const std::string& s, long an, double bal, double ml, double r) :Brass(s, an, bal), maxLoan(ml), rate(r) {}
BrassPlus::BrassPlus(const Brass& ba, double ml, double r) :Brass(ba), maxLoan(ml), rate(r) {}
void BrassPlus::ViewAcct()const { cout << "BrassPlus viewacct" << endl; }
void BrassPlus::withdraw(double amt) { cout << "BrassPlus withdraw: " << amt << endl; return; }
void BrassPlus::SecondMethod() { cout << "BrassPlus SecondMethod: " << endl; return; }

void test(const Brass& br) {
    br.ViewAcct();
    //br.withdraw(6.0);//编译不通,const 实例只能调用const成员函数;
    Brass& bbp = const_cast<Brass&>(br);
    bbp.withdraw(6.0);
}

typedef void(Brass::*Fun)();
typedef void(Brass::*Fun1)(double);
typedef void(BrassExtra::*Fun2)();
int main()
{
#ifdef _WIN64
	typedef __int64 ADTY;//64位操作系统,8个字节表示一个地址
#else
	typedef __int32 ADTY;
#endif
	cout << "Hello World!" << endl;
	Brass bra("Jim", 2, 3.06);
	BrassPlus brap("Jhon", 5, 0.58, 500, 0.014);
	const Brass bra_2("Jack", 3, 0.02);
	Brass& b1 = bra;
	Brass& b2 = brap;
	Brass* bas[2];
	bas[0] = &bra;
	bas[1] = &brap;
	cout << "对象bas[0]的虚函数表位于:" << hex << *(ADTY*)bas[0] << ",对象bas[0]的第一个虚函数位于:" << hex << *((ADTY*)*(ADTY*)bas[0] + 0) << endl;
	cout << "对象bas[1]的第一张虚函数表位于:" << hex << *(ADTY*)bas[1] << ",对象bas[1]的第一张虚函表的第一个虚函数位于:" << hex << *((ADTY*)*(ADTY*)bas[1] + 0) << endl;

	//虚指针后面放Brass数据,由于Brass的字节对齐参数为8, 因此如果虚表指针为4字节则需要在虚表指针后面额外填充4字节)
	cout << *(string*)((char*)bas[1] + sizeof(ADTY) + 4) << endl;
	cout << *(long*)(((char*)((char*)bas[1] + sizeof(ADTY) + 4 ) + sizeof(string)) + 0) << endl;//Brass::acctNum
	cout << *(double*)(((char*)((char*)bas[1] + sizeof(ADTY) + 4 ) + sizeof(string)) + sizeof(long) +0) << endl;//Brass::balance

	//如果是win64 Release, sizeof(string) 为32,考虑内存对齐,需要在actNum后面填充4字节。本测试是在Win32 Debug配置下测试,sizeof(string) 为28, 无需填充
	cout << "对象bas[1]的第二张虚函数表位于:" << hex << *(ADTY*)(((char*)((char*)bas[1] + sizeof(ADTY) + 4) + sizeof(string)) + sizeof(long) /*+ 4*/ + sizeof(double)) << endl;
	printf("%d\n", *(int*)(((char*)((char*)bas[1] + sizeof(ADTY) + 4) + sizeof(string)) + sizeof(long)/* + 4*/ + sizeof(double) + sizeof(ADTY)));//BrassExtra::vvk
	cout << "子类虚函数表会从父类继承(复制一份,不是共用一份),如果子类重写了某个虚函数,则子类的虚函数表中对应的函数地址会被覆盖" << endl;

	// 第一张虚函数表调用
	Fun cc = (Fun)*((Fun*)*(ADTY*)&brap + 0);
	Fun1 dd = (Fun1)*((Fun1*)*(ADTY*)&brap + 1);
	cout << "对象bas[1]虚函数调用结果:" << endl;
	(brap.*cc)();
	(brap.*dd)(8.9);  

    // 第二张虚函数表调用
	Fun2 bvvv = (Fun2)(*((Fun2*)*(ADTY*)(((char*)((char*)&brap + sizeof(ADTY) + 4) + sizeof(string)) + sizeof(long)/* + 4*/ + sizeof(double)) + 0));//对象bas[1]的第二个虚函表的第一个虚函数
	(brap.*bvvv)();


	ADTY* ptr = (ADTY*)(&bra);
	//可以看出虚函数表类似于类的静态成员,对象的首地址位置保存了虚函数表的地址
	cout << "对象bra的虚函数表位于:" << hex << *ptr << ",对象bra的第一个虚函数位于:" << hex << *((ADTY*)*ptr + 0) << endl;
	cout << "对象bra_2的虚函数表位于:" << hex << *(ADTY*)(&bra_2) << ",对象bra_2的第一个虚函数位于:" << hex << *((ADTY*)*(ADTY*)(&bra_2) + 0) << endl;
	cout << "说明bra的虚函数表 与 bra_2的虚函数表 是同一张表" << endl;

	ADTY* ptr1 = (ADTY*)*ptr;
	Fun pFun = (Fun)*((Fun*)ptr1 + 0); //等效于 (Fun)(*((Fun*)*(ADTY*)(&bra)+0));
	(bra.*pFun)();
	Fun1 pFun1 = (Fun1)*((Fun1*)ptr1 + 1); //等效于 (Fun1)(*((Fun1*)*(ADTY*)(&bra)+1));
	(brap.*pFun1)(9.0);
	cout << "Hello World end" << endl;
	return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值