网站建立项目步骤,企业网站建设的流程与原则,研究院网站系统建设方案,门户网站建设经济交流材料目录 深入理解C 虚函数表虚函数表概述单继承下的虚函数表派生类未覆盖基类虚函数派生类覆盖基类虚函数多继承下的虚函数表无虚函数覆盖派生类覆盖基类虚函数钻石型虚继承总结几个原则安全性问题深入理解C 虚函数表 C中的虚函数的作用主要是实现了多态的机制。关于多态#… 目录 深入理解C 虚函数表虚函数表概述单继承下的虚函数表派生类未覆盖基类虚函数派生类覆盖基类虚函数多继承下的虚函数表无虚函数覆盖派生类覆盖基类虚函数钻石型虚继承总结几个原则安全性问题 深入理解C 虚函数表 C中的虚函数的作用主要是实现了多态的机制。关于多态简而言之就是用父类型别的指针指向其子类的实例然后通过父类的指针调用实际子类的成员函数。 Derive d;
Base1 *b1 d;
Base2 *b2 d;
Base3 *b3 d;
b1-f(); //Derive::f()
b2-f(); //Derive::f()
b3-f(); //Derive::f()b1-g(); //Base1::g()
b2-g(); //Base2::g()
b3-g(); //Base3::g() 这种技术可以让父类的指针有“多种形态”这是一种泛型技术。所谓泛型技术说白了就是试图使用不变的代码来实现可变的算法。比如模板技术RTTI技术虚函数技术要么是试图做到在编译时决议要么试图做到运行时决议。 本文将详细介绍虚函数表的实现及其内存布局。 虚函数表概述 虚函数表是指在每个包含虚函数的类中都存在着一个函数地址的数组。当我们用父类的指针来操作一个子类的时候这张虚函数表指明了实际所应该调用的函数。 C的编译器保证虚函数表的指针存在于对象实例中最前面的位置这样通过对象实例的地址得到这张虚函数表然后就可以遍历其中函数指针并调用相应的函数。 按照上面的说法来看一个实际的例子 #include iostreamusing namespace std;class Base {
public:virtual void f() { cout f() endl; }virtual void g() { cout g() endl; }virtual void h() { cout h() endl; }
};int main()
{Base t;( ((void(*)())*((int*)(*((int*)t)) 0)) ) ();( ((void(*)())*((int*)(*((int*)t)) 1)) ) ();( ((void(*)())*((int*)(*((int*)t)) 2)) ) ();return 0;
} 经过VS2017x86测试 我们成功地通过实例对象的地址得到了对象所有的类函数。 main定义Base类对象t把b转成int *取得虚函数表的地址vtptr就是(int*)(t)然后再解引用并强转成int *得到第一个虚函数的地址也就是Base::f()即(int*)(*((int*)t))那么第二个虚函数g()的地址就是(int*)(*((int*)t)) 1依次类推。 单继承下的虚函数表 派生类未覆盖基类虚函数 下面我们来看下派生类没有覆盖基类虚函数的情况其中Base类延用上一节的定义。从图中可看出虚函数表中依照声明顺序先放基类的虚函数地址再放派生类的虚函数地址。 可以看到下面几点 1虚函数按照其声明顺序放于表中。 2父类的虚函数在子类的虚函数前面。 测试代码 #include iostreamusing namespace std;class Base {
public:virtual void f() { cout f() endl; }virtual void g() { cout g() endl; }virtual void h() { cout h() endl; }
};class Devired :public Base{
public:virtual void x() { cout x() endl; }
};int main()
{Devired t;(((void(*)()) *((int*)(*((int*)t))))) ();(((void(*)())*((int*)(*((int*)t)) 1))) ();(((void(*)())*((int*)(*((int*)t)) 2))) ();//(((void(*)())*((int*)(*((int*)t)) 3))) ();return 0;
} 测试效果 派生类覆盖基类虚函数 再来看一下派生类覆盖了基类的虚函数的情形可见 虚表中派生类覆盖的虚函数的地址被放在了基类相应的函数原来的位置 显然的不然虚函数失去意义派生类没有覆盖的虚函数延用基类的测试代码 #include iostreamusing namespace std;class Base {
public:virtual void f() { cout f() endl; }virtual void g() { cout g() endl; }virtual void h() { cout h() endl; }
};class Derive :public Base{
public:virtual void x() { cout x() endl; }virtual void f() { cout Derive::f() endl; }
};int main()
{Derive t;(((void(*)()) *((int*)(*((int*)t))))) ();(((void(*)())*((int*)(*((int*)t)) 1))) ();(((void(*)())*((int*)(*((int*)t)) 2))) ();//(((void(*)())*((int*)(*((int*)t)) 3))) ();return 0;
} 测试效果 多继承下的虚函数表 无虚函数覆盖 如果是多重继承的话问题就变得稍微复杂一丢丢主要有几点 每个基类都有自己的虚函数表派生类的虚函数地址存依照声明顺序放在第一个基类的虚表最后这点和单继承无虚函数覆盖相同具体见下图所示 测试代码 #include iostream
class Base
{
public:Base(int mem1 1, int mem2 2) : m_iMem1(mem1), m_iMem2(mem2) { ; }virtual void vfunc1() { std::cout In vfunc1() std::endl; }virtual void vfunc2() { std::cout In vfunc2() std::endl; }virtual void vfunc3() { std::cout In vfunc3() std::endl; }private:int m_iMem1;int m_iMem2;
};class Base2
{
public:Base2(int mem 3) : m_iBase2Mem(mem) { ; }virtual void vBase2func1() { std::cout In Base2 vfunc1() std::endl; }virtual void vBase2func2() { std::cout In Base2 vfunc2() std::endl; }private:int m_iBase2Mem;
};class Base3
{
public:Base3(int mem 4) : m_iBase3Mem(mem) { ; }virtual void vBase3func1() { std::cout In Base3 vfunc1() std::endl; }virtual void vBase3func2() { std::cout In Base3 vfunc2() std::endl; }private:int m_iBase3Mem;
};class Devired : public Base, public Base2, public Base3
{
public:Devired(int mem 7) : m_iMem1(mem) { ; }virtual void vdfunc1() { std::cout In Devired vdfunc1() std::endl; }private:int m_iMem1;
};int main()
{// Test_3Devired d;int *dAddress (int*)d;typedef void(*FUNC)();/* 1. 获取对象的内存布局信息 */// 虚表地址一int *vtptr1 (int*)*(dAddress 0);int basemem1 (int)*(dAddress 1);int basemem2 (int)*(dAddress 2);int *vtpttr2 (int*)*(dAddress 3);int base2mem (int)*(dAddress 4);int *vtptr3 (int*)*(dAddress 5);int base3mem (int)*(dAddress 6);/* 2. 输出对象的内存布局信息 */int *pBaseFunc1 (int *)*(vtptr1 0);int *pBaseFunc2 (int *)*(vtptr1 1);int *pBaseFunc3 (int *)*(vtptr1 2);int *pBaseFunc4 (int *)*(vtptr1 3);(FUNC(pBaseFunc1))();(FUNC(pBaseFunc2))();(FUNC(pBaseFunc3))();(FUNC(pBaseFunc4))();// .... 后面省略若干输出内容可自行补充return 0;
} 测试效果 派生类覆盖基类虚函数 我们再来看一下派生类覆盖了基类的虚函数的情形可见 虚表中派生类覆盖的虚函数的地址被放在了基类相应的函数原来的位置派生类没有覆盖的虚函数延用基类的代码如下所示注意这里只给出了类的定义main函数的测试代码与上节一样 class Devired : public Base, public Base2, public Base3
{
public:Devired(int mem 7) : m_iMem1(mem) { ; }virtual void vdfunc1() { std::cout In Devired vdfunc1() std::endl; }virtual void vfunc1() { std::cout In Devired vfunc1() std::endl; }virtual void vBase2func1() { std::cout In Devired vfunc1() std::endl; }private:int m_iMem1;
}; 测试效果 钻石型虚继承 该继承还是遵循上述的所有原则我们直接来测试。 测试代码 // 测试四钻石型虚继承//虚基指针所指向的虚基表的内容
// 1. 虚基指针的第一条内容表示的是该虚基指针距离所在的子对象的首地址的偏移
// 2. 虚基指针的第二条内容表示的是该虚基指针距离虚基类子对象的首地址的偏移#pragma vtordisp(off)
#include iostream
using std::cout;
using std::endl;class B
{
public:B() : _ib(10), _cb(B) {}virtual void f(){cout B::f() endl;}virtual void Bf(){cout B::Bf() endl;}private:int _ib;char _cb;
};class B1 : virtual public B
{
public:B1() : _ib1(100), _cb1(1) {}virtual void f(){cout B1::f() endl;}#if 1virtual void f1(){cout B1::f1() endl;}virtual void Bf1(){cout B1::Bf1() endl;}
#endifprivate:int _ib1;char _cb1;
};class B2 : virtual public B
{
public:B2() : _ib2(1000), _cb2(2) {}virtual void f(){cout B2::f() endl;}
#if 1virtual void f2(){cout B2::f2() endl;}virtual void Bf2(){cout B2::Bf2() endl;}
#endif
private:int _ib2;char _cb2;
};class D : public B1, public B2
{
public:D() : _id(10000), _cd(3) {}virtual void f(){cout D::f() endl;}#if 1virtual void f1(){cout D::f1() endl;}virtual void f2(){cout D::f2() endl;}virtual void Df(){cout D::Df() endl;}
#endif
private:int _id;char _cd;
};int main(void)
{D d;cout sizeof(d) endl;return 0;
}测试效果 1class D size(52):
1 ---
1 0 | --- (base class B1)
1 0 | | {vfptr}
1 4 | | {vbptr}
1 8 | | _ib1
112 | | _cb1
1 | | alignment member (size3)
1 | ---
116 | --- (base class B2)
116 | | {vfptr}
120 | | {vbptr}
124 | | _ib2
128 | | _cb2
1 | | alignment member (size3)
1 | ---
132 | _id
136 | _cd
1 | alignment member (size3)
1 ---
1 --- (virtual base B)
140 | {vfptr}
144 | _ib
148 | _cb
1 | alignment member (size3)
1 ---
1
1D::$vftableB1:
1 | D_meta
1 | 0
1 0 | D::f1
1 1 | B1::Bf1
1 2 | D::Df
1
1D::$vftableB2:
1 | -16
1 0 | D::f2
1 1 | B2::Bf2
1
1D::$vbtableB1:
1 0 | -4
1 1 | 36 (Dd(B14)B)
1
1D::$vbtableB2:
1 0 | -4
1 1 | 20 (Dd(B24)B)
1
1D::$vftableB:
1 | -40
1 0 | D::f
1 1 | B::Bf
1 总结 几个原则 单继承 虚表中派生类覆盖的虚函数的地址被放在了基类相应的函数原来的位置派生类没有覆盖的虚函数就延用基类的。同时虚函数按照其声明顺序放于表中父类的虚函数在子类的虚函数前面。多继承 每个基类都有自己的虚函数表派生类的虚函数地址存依照声明顺序放在第一个基类的虚表最后安全性问题 当我们直接通过父类指针调用子类中的未覆盖父类的成员函数编译器会报错但通过实验我们可以用对象的地址访问到各个子类的成员函数就违背了C语义操作会有一定的隐患当我们使用时要注意这些危险的东西 参考 https://coolshell.cn/articles/12165.html https://jocent.me/2017/08/07/virtual-table.html https://blog.csdn.net/lihao21/article/details/50688337转载于:https://www.cnblogs.com/Mered1th/p/10924545.html