尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

c++ 继承关系中的虚函数表 1. 子类继承父类的虚函数表的方式是复制一份。存在虚函数的类都有自己的虚函数表不与其他类共用。2. 只要祖先类的某个函数被声明为virtual, 则在后代中无论是否显式地添加virtual该函数一直都是虚的。3. 如果子类重写了某个虚函数则该类的虚函数表对应位置上的虚函数地址会被覆盖。如果完全不重写则子类的虚函数表里面的内容和父类虚函数表内容保持一致。(需要注意的是虽然父类的函数地址被覆盖了但是默认的参数还是保留了如果用父类指针去调用子类重载的函数传的默认参数值还是父类给的默认值只有用子类本身类型的指针去调用才会传子类给的默认值)。4. 虚函数表类似于类的静态成员被该类的所有实例共享。只不过该表是只读的不存在线程安全问题。5. 实例对象在起始4字节中会存放虚函数表的地址(跟系统寻址范围有关有的系统8字节才能表示一个地址)可以通过手动寻址的方式完成虚函数调用。6. 多重继承(有多个父类并列)时该类有多张虚函数表实例对象的0~3字节表示第一个虚函数表地址4~n字节表示第一个父类的成员变量n1~n4表示第二张虚函数表地址n5~m表示第二个父类成员变量以此类推(C对象内存模型)。当这些父类又有共同祖先时为避免数据冗余和冲突继承方式应该是虚继承。7. 用父类指针指向子类对象时指针地址会根据多重继承顺序的发生相应的偏移。如A:B,C表示A同时继承了B和C当用B*类型指向A类对象时指针偏移0字节当用C类型指向A类对象时指针偏移sizeof(B)字节。用代码表示如下#ifdef __WIN64typedef __int64 ADTY;#elsetypedef __int32 ADTY;#endifA 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多态指针时如果发现指针类型的析构函数为虚编译器将从虚函数表中查找虚析构函数发起调用否则直接调用当前类型析构。(编译期间派生类虚表已经定型虚表中的析构函数为派生类自己的析构所以不论是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_castBrass(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个字节表示一个地址 int nalign 0; int pad 4; #else typedef __int32 ADTY; int nalign 4; int pad 0; #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) nalign) endl; cout *(long*)(((char*)((char*)bas[1] sizeof(ADTY) nalign ) sizeof(string)) 0) endl;//Brass::acctNum cout *(double*)(((char*)((char*)bas[1] sizeof(ADTY) nalign ) 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) nalign) sizeof(string)) sizeof(long) pad sizeof(double)) endl; printf(%d\n, *(int*)(((char*)((char*)bas[1] sizeof(ADTY) nalign) sizeof(string)) sizeof(long) pad 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) nalign) sizeof(string)) sizeof(long) pad 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; }
返回列表