您的位置:首页 > 编程语言 > C语言/C++

C++ 虚函数剖析

2016-11-18 15:07 169 查看

虚函数之虚表

带有虚函数的类的对象模型剖析

(编译环境Vs2013)
看到这个题目,很多的人可能很想知道什么是虚表????为什么要说这个东西呢???这个虚表有什么作用呢????
当然,先容我买个关子,后面再说,,,,现在我们先来看看带有虚函数类的结构剖析:::
大家来看看一段代码:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class B
{
public:
B(int b  = 0 )
:_b(b)
{
cout << "B::B(int b  = 0 )" << endl;
}
void Test1()
{
cout << "void B::Test1()" << endl;
}
virtual void Test2()
{
cout << "void B::Test2()" << endl;
}
virtual void Test3()
{
cout << "void B::Test3()" << endl;
}
virtual void Test4()
{
cout << "void B::Test4()" << endl;
}

private:
int _b;
};

int main()
{
B b(1);
int a = sizeof(b) ;
cout << a << endl;
return 0;
}

这段代码中定义了一个B类类中    包含 成员变量_b ,1个成员函数Test1 ,3个虚函数Test2、Test3、Test4
代码输出后结果得到:


  
可以看到得到的B类对象的大小为   8,按照之前的类的大小计算,只应包含一个成员变量的大小为4,为什么还多出了4字节呢????
那就然我们来看看编译器在内存中的存储吧!!!!!!!!!!!!



从编译器的内存调试窗口中 ,我们可以明显看出   B类对象b包含两部分:
一部分呢是   一串数字(猜测可能是存了个地址)
另一部分呢  就是B类对象的成员变量  在内存保存的是给的1     ;;;;
第一部分看上去是个指针 ,那我们就当他们是指针 ,里面保存的是地址 ,,,
如果保存的是地址,那么这个 地址里保存的是什么呢?????


查看这段地址后 ,得到了这幅图;;;;;

在这段地址里,保存了有四部分,前三部分像是地址 ,第四部分是 0 ;
正好在这个类中有三个虚函数,我们这三个地址保存的是三个虚函数的地址,0表示结束。。。
既然假设了之后 ,,,,我们就 得验证一下,但要怎么验证呢?????
其实我们可通过反证法来试试,,,,我们就当它们保存的是虚函数,那么我们只需定义一个函数指针
用这三个地址来调用,看看结果,,,就知道是不是代表的是虚函数的地址》》》》》》
代码如下:
typedef void(*ptest)();//定义个函数指针ptest

int main()
{
B b(1);

ptest *p = (ptest*)(*((int*)(&b)));//p表示的是指向函数指针的指针;
while (*p)//当遇到我们在内存中看到的数字0
{
(*p)();
p++;
}
return 0;
}
最后输出的结果输出为



通过输出的结构我们可以得出结论:这些地址表示的就是虚函数的地址。。。。。
在此得出结论后 ,我们基本了解到
带有虚函数的类在构造函数时,,,会先为对象空间中首位放上一个指针, 内部存储的是    一个表格
这个表格   内部存储的是类中虚函数的地址 ,并且在结束时,保存一个0 ;;;;
我们把这个表叫做是          虚表
那么开头遇到的问题是不是就解决了




带有虚函数的继承的对象模型剖析

上面我们基本了解到了什么是虚表,虚表要怎么存储????
但是上面的知识基础知识 ,,,,,
我们都知道,虚函数是用来实现多态的,多态肯定要会包含到继承,,,,
那么,接下来我们就来看看,在继承中中的虚表存储

单继承虚表

我们先从简单的单继承来说起!!!!!!基类对象的结构剖析,我们之前也了解到了,那么我们现在主要说说派生类中的虚表。
先看一段代码:
class B
{
public:
B(int b = 0)
:_b(b)
{}
void Test1()
{
cout << "void B::Test1()" << endl;
}
virtual void Test2()
{
cout << "void B::Test2()" << endl;
}
virtual void Test3()
{
cout << "void B::Test3()" << endl;
}
virtual void Test4()
{
cout << "void B::Test4()" << endl;
}

private:
int _b;
};

class D : public B
{
public:
D(int b = 10, int d = 20)
:B(b)
, _d(d)
{}
virtual void Test2()
{
cout << "void D::Test2()" << endl;
}
virtual void Test3()
{
cout << "void D::Test3()" << endl;
}
virtual void Test5()
{
cout << "void D::Test5()" << endl;

}
private:
int _d ;
};
int main()
{
B b(1);
D d;
cout << sizeof(d) << endl;
return 0;
}
这段代码中,对于类D继承B类 ,,,并且对于基类中 的虚函数Test2 ,Test3进行了重写。,
又加了一个虚函数Test5
最后得到的类D的对象的大小为12 ;; ; ;
D类对象在内存中的存储为:


   
 从图中可以看出   对象中包含了虚表的地址0x0113dd6c   还有 基类 ,派生类自己的成员变量
接下来我们看看这个虚表和我们在基类中看到的有什么不同!!!!!!


虚表中明显多了一个地址, 那么多的这个地址就只会是Test5的地址了》》
通过函数指针来,调用这些函数看看》》》》
typedef void(*ptest)();//定义个函数指针ptest
int main()
{
B b(1);
D d;
ptest *p = (ptest*)(*((int*)(&d)));//p表示的是指向函数指针的指针;
while (*p)//当遇到我们在内存中看到的数字0
{
(*p)();
p++;
}
return 0;
生成的结果为::::



从图上我们可以看出,
Test2   ,Test3 调用的是派生类自己的,  Test4调用的任然是基类的, Test5只能调用派生类的。。。。。
从中我们可以知道,派生类的在构造对象时,先调用基类的构造函数,
然后对于虚表进行了一定程度上的修改。。。

多继承虚表

上面我们初步了解到了在继承中派生类对象虚表的模型 , ,,  ,下面我们来看看多继承的虚表的结构。。。
下面的代码就是说多继承的虚表
class B1
{
public:
B1(int b = 0)
:_b1(b)
{}
virtual void Test2()
{
cout << "void B1::Test2()" << endl;
}
virtual void Test4()
{
cout << "void B1::Test4()" << endl;
}

private:
int _b1;
};
class B2
{
public:
B2(int b2 = 2)
:_b2(b2)
{}
virtual void Test3()
{
cout << "void B2::Test3()" << endl;
}
virtual void Test5()
{
cout << "void B2::Test5()" << endl;
}

private:
int _b2;
};

class D : public B1,public B2
{
public:
D(int b1 = 10,int b2 = 20, int d = 30)
:B1(b1)
,B2(b2)
, _d(d)
{}
virtual void Test2()
{
cout << "void D::Test2()" << endl;
}
virtual void Test5()
{
cout << "void D::Test5()" << endl;

}
virtual void Test6()
{
cout << "void D::Test6()" << endl;

}
private:
int _d ;
};
int main()
{
D d;
return 0;
}

简单介绍基类  B1, B2,类  派生类D
B1  ::  成员变量_b1; 虚函数Test2,Test3
B2  ::  成员变量_b2; 虚函数Test4,Test5

D:::  成员变量 _d;; 对基类B1的Test2  ,基类B2的Test5 进行重写  ;;;添加派生类自己的虚函数Test6
最后生成的D类对象   ——d的内存为



在图中我们可以大致将它分为三部分:::::
1、  B1类对象  (B1的虚表, B1的成员)
2、  B2类对象  (B2的虚表, B2的成员)
3、  D  类对象 
我们可以调用这两个虚表来看看内部保存的虚函数   





调用这些函数地址:
typedef void(*ptest)();//定义个函数指针ptest
int main()
{
D d;
ptest *p1 = (ptest*)(*((int*)(&d)));//p表示的是指向函数指针的指针;
while (*p1)//当遇到我们在内存中看到的数字0
{
(*p1)();
p1++;
}
cout << endl;
ptest *p2 = (ptest*)*(((int*)(&d)) + 2);//p表示的是指向函数指针的指针;
while (*p2)//当遇到我们在内存中看到的数字0
{
(*p2)();
p2++;
}
return 0;
}

生成的结果:



通过这个结果我们可以得出结论:
派生类对象在构造时》》》》》》  ,先构造 两个基类 ,然后依次对每个基类的虚表进行改写 , ,, , ,

关于派生类自己的虚函数地址,,,,,一般放在继承的基类的虚表中。 。 。。 。

菱形继承对象模型剖析

 说到菱形继承 , , 一般都离不开虚拟继承的。。。。。
如果要是不知什么是虚继承的话 , , ,可以看这篇博客C++之虚拟继承
为什么要单独说这个 菱形继承呢 ???
主要是因为,在虚拟继承时生成的类会在类对象之前也加上一个指针。。。。内部存的是
1、对象首位置到指针的偏移。
2、基类对象到指针的偏移。。。。
下面是一段带有虚函数的菱形继承(虚继承)的代码;;;;
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class B
{
public:
B(int b = 1)
:_b(b)
{}
virtual void Test1()
{
cout << "void B::Test1()" << endl;
}
virtual void Test2()
{
cout << "void B::Test2()" << endl;
}
private:
int _b;
};

class C1 :virtual public B
{
public:
C1(int b = 1,int c1= 2)
:B(b)
, _c1(c1)
{}
virtual void Test2()
{
cout << "void C1::Test2()" << endl;
}

private:
int _c1;
};

class C2 :virtual public B
{
public:
C2(int b = 1, int c2 = 3)
:B(b)
, _c2(c2)
{}
virtual void Test1()
{
cout << "void C2::Test1()" << endl;
}

private:
int _c2;
};

class D :public C1, public C2
{
public:
D(int d = 4)
:C1()
,C2()
, _d(d)
{}
virtual void Test1()
{
cout << "void D::Test1()" << endl;
}
virtual void Test2()
{
cout << "void D::Test2()" << endl;
}

private:
int _d;
};

typedef void (*ptest)();
int main()
{
C1 c1;
C2 c2;
D d;
ptest *p1 = (ptest *)*((int *)(&c1)+3);
while (*p1)
{
(*p1)();
p1++;
}
ptest *p2 = (ptest *)*((int *)(&c2) + 3);
while (*p2)
{
(*p2)();
p2++;
}
ptest *p3 = (ptest *)*((int *)(&d) + 6);
while (*p3)
{
(*p3)();
p3++;
}
cout << sizeof(c1) << endl;
cout << sizeof(c2) << endl;
cout << sizeof(d)  << endl;
return 0;
}
代码简要说明:
B类 :成员变量  _b,,,带有虚函数Test1,Test2;
C1类(继承B类):成员变量_c1,对Test2,进行重写;;;
C2类(继承B类):成员变量_c2,对Test1,进行重写;;i,;
D类(多继承C1,C2类):成员变量_d,对Test1,Test2进行重写;;;;
根据代码我们生成的类的大小分别为 
C1   20;
C2   20;
D     32;
在内存中对象的存储分别为:
C1   与C2 对象





从图中我们可以看出两个对象可以分为5个部分:
1、一个指针;;
2、C1 、C2类自己的成员;
3、一个0;
4、一个地址;
5、基类的成员;;;;
我们以C1对象为例,来看看上面的两个地址表示的到底是什么????
第一个地址:



第二个地址:



从两幅图中我们可以得出结论:
第一个地址表示的是虚拟继承得到的地址;
第二个地址表示的是虚函数的虚表的地址。
因此我们 可以大致猜测得到一个结论::::
(带有虚函数的)菱形继承的C1,与 C类的结构模型大致为:::::



那么再来看看D类对象的成员分布:
D类是一个继承两个子类,,,,
内存中的分布为


             
从图中类可以分为六部分:
1、C1的成员;
2、C2的成员;
3、D类自己的成员;
4、00 00 00 00 
5、指针;
6、B类的成员。。。。。
我们从中可以得知的是上面的指针表示的是虚表的地址
因此我们可以的得到,菱形继承生成的D类的模型结构为:::::


    这个图是相对于这段代码来写的;;;;;;
关于在内存中保存的     00  00  00  00 到底表示的是什么意思????
就留给大家下面来探讨吧!!!!!!!!
代码最后生成的结果::::

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  虚表