码迷,mamicode.com
首页 > 其他好文 > 详细

看到的关于虚函数继承及虚继承解释比较好的文章的复制

时间:2016-08-07 23:05:39      阅读:254      评论:0      收藏:0      [点我收藏+]

标签:

(来源于:http://blog.chinaunix.net/uid-25132162-id-1564955.html)
1、空类,空类单继承,空类多继承的sizeof
  1. #include <iostream>
  2. using namespace std;
  3. class Base1
  4. {
  5. };
  6. class Base2
  7. {
  8. };
  9. class Derived1:public Base1
  10. {
  11. };
  12. class Derived2:public Base1, public Base2
  13. {
  14. };
  15. int main() 
  16.     Base1 b1;
  17.     Base2 b2;
  18.     Derived1 d1;
  19.     Derived2 d2;
  20.     cout<<"sizeof(Base1) = "<<sizeof(Base1)<<" sizeof(b1) = "<<sizeof(b1)<<endl;
  21.      cout<<"sizeof(Base2) = "<<sizeof(Base2)<<" sizeof(b2) = "<<sizeof(b2)<<endl;
  22.     cout<<"sizeof(Derived1) = "<<sizeof(Derived1)<<" sizeof(d1) = "<<sizeof(d1)<<endl;
  23.     cout<<"sizeof(Derived2) = "<<sizeof(Derived2)<<" sizeof(d1) = "<<sizeof(d1)<<endl;
  24.   
  25.     return 0; 
  26. }
结果为:
sizeof(Base1) = 1 sizeof(b1) = 1
sizeof(Base2) = 1 sizeof(b2) = 1
sizeof(Derived1) = 1 sizeof(d1) = 1
sizeof(Derived2) = 1 sizeof(d1) = 1
可以看出所有的结果都是1。
 
2、含有虚函数的类以及虚继承类的sizeof
虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。

假设我们有这样的一个类:

class Base {

public:

virtual void f() { cout << "Base::f" << endl; }

virtual void g() { cout << "Base::g" << endl; }

virtual void h() { cout << "Base::h" << endl; }

};

当我们定义一个这个类的实例,Base b时,其b中成员的存放如下:

技术分享指向虚函数表的指针在对象b的最前面。

虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

因为对象b中多了一个指向虚函数表的指针,而指针的sizeof是4,因此含有虚函数的类或实例最后的sizeof是实际的数据成员的sizeof加4。

下面将讨论针对基类含有虚函数的继承讨论

(1)在派生类中不对基类的虚函数进行覆盖,同时派生类中还拥有自己的虚函数,比如有如下的派生类:

class Derived: public Base

 {

public:

virtual void f1() { cout << "Derived::f1" << endl; }

virtual void g1() { cout << "Derived::g1" << endl; }

virtual void h1() { cout << "Derived::h1" << endl; }

};

基类和派生类的关系如下:

技术分享

当定义一个Derived的对象d后,其成员的存放如下:

技术分享

可以发现:

       1)虚函数按照其声明顺序放于表中。

       2)父类的虚函数在子类的虚函数前面。

此时基类和派生类的sizeof都是数据成员的sizeof加4。

(2)在派生类中对基类的虚函数进行覆盖,假设有如下的派生类:

class Derived: public Base

 {

public:

virtual void f() { cout << "Derived::f" << endl; }

virtual void g1() { cout << "Derived::g1" << endl; }

virtual void h1() { cout << "Derived::h1" << endl; }

};

基类和派生类之间的关系:其中基类的虚函数f在派生类中被覆盖了

技术分享

当我们定义一个派生类对象d后,其d的成员存放为:

技术分享

可以发现:

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

这样,我们就可以看到对于下面这样的程序,

Base *b = new Derive();

b->f();

由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

(3)多继承:无虚函数覆盖

假设基类和派生类之间有如下关系:

技术分享

对于子类实例中的虚函数表,是下面这个样子:

技术分享

我们可以看到:

1) 每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

由于每个基类都需要一个指针来指向其虚函数表,因此d的sizeof等于d的数据成员加3*4=12。

(4)多重继承,含虚函数覆盖

假设,基类和派生类又如下关系:派生类中覆盖了基类的虚函数f

技术分享

下面是对于子类实例中的虚函数表的图:

技术分享

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

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()

3、一个关于含虚函数及虚继承的sizeof计算

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. class Base
  5. {
  6. public:
  7.     virtual void f();
  8.     virtual void g();
  9.     virtual void h();
  10. };
  11.  
  12. class Derived1: public Base
  13. {
  14. public:
  15.     virtual void f1();
  16.     virtual void g1();
  17.     virtual void h1();
  18. };
  19.  
  20. class Derived2:public Base
  21. {
  22. public:
  23.     virtual void f();
  24.     virtual void g1();
  25.     virtual void h1();
  26. };
  27.  
  28. class Derived3:virtual public Base
  29. {
  30. public:
  31.     virtual void f1();
  32.     virtual void g1();
  33.     virtual void h1();
  34. };
  35.  
  36. class Derived4:virtual public Base
  37. {
  38. public:
  39.     virtual void f();
  40.     virtual void g1();
  41.     virtual void h1();
  42. };
  43.  
  44. class Derived5:virtual public Base
  45. {
  46. public:
  47.     virtual void f();
  48.     virtual void g();
  49.     virtual void h();
  50. };
  51.  
  52. class Derived6:virtual public Base
  53. {
  54.  
  55. };
  56.  
  57. int main() 

  58.     cout<<sizeof(Base)<<endl; //4
  59.     cout<<sizeof(Derived1)<<endl; //4
  60.     cout<<sizeof(Derived2)<<endl; //4
  61.     cout<<sizeof(Derived3)<<endl; //12
  62.     cout<<sizeof(Derived4)<<endl; //12
  63.     cout<<sizeof(Derived5)<<endl; //8
  64.     cout<<sizeof(Derived6)<<endl; //8
  65.  
  66.     return 0; 
  67. }
对于Base, Derived1和Derived2的结果根据前面关于继承的分析是比较好理解的,不过对于虚继承的方式则有点不一样了,根据结果自己得出的一种关于虚继承的分析,如对Derived3或Derived4定义一个对象d,其里面会出现三个跟虚函数以及虚继承的指针,因为是虚继承,因此引入一个指针指向虚继承的基类,第二由于在基类中有虚函数,因此需要指针指向其虚函数表,由于派生类自己本身也有自己的虚函数,因为采取的是虚继承,因此它自己的虚函数不会放到基类的虚函数表的后面,而是另外分配一个只存放自己的虚函数的虚函数表,于是又引入一个指针,从例子中看到Derived5和Derived6的结果是8,原因是在派生类要么没有自己的虚函数,要么全部都是对基类虚函数的覆盖,因此就少了指向其派生类自己的虚函数表的指针,故结果要少4。(这个是个人的分析,但原理不知道是不是这样的)
 
 
 

(参考:http://www.cnblogs.com/fanzhidongyzby/archive/2013/01/14/2859064.html)

总结虚函数调用形式,应该是:

*(this指针+调整量)[虚函数在vftable内的偏移]()

 

 

虚拟继承是为了解决多重继承下公共基类的多份拷贝问题。比如上边的例子中MyClassC的对象内包含MyClassA和MyClassB子对象,但是MyClassA和MyClassB内含有共同的基类MyClass。为了消除MyClass子对象的多份存在,我们需要让MyClassA和MyClassB都虚拟继承于MyClass,然后再让MyClassC多重继承于这两个父类。

class MyClassA:virtual public MyClass
class MyClassB:virtual public MyClass
class MyClassC:public MyClassA,public MyClassB

 

 

虚继承的引入把对象的模型变得十分复杂,除了每个基类(MyClassA和MyClassB)和公共基类(MyClass)的虚函数表指针需要记录外,每个虚拟继承了MyClass的父类还需要记录一个虚基类表vbtable的指针vbptr。MyClassC的对象模型如图4所示。

技术分享

图4 MyClassC对象模型

虚基类表每项记录了被继承的虚基类子对象相对于虚基类表指针的偏移量。比如MyClassA的虚基类表第二项记录值为24,正是MyClass::vfptr相对于MyClassA::vbptr的偏移量,同理MyClassB的虚基类表第二项记录值12也正是MyClass::vfptr相对于MyClassA::vbptr的偏移量。

和虚函数表不同的是,虚基类表的第一项记录着当前子对象相对与虚基类表指针的偏移。MyClassA和MyClassB子对象内的虚表指针都是存储在相对于自身的4字节偏移处,因此该值是-4。假定MyClassA和MyClassC或者MyClassB内没有定义新的虚函数,即不会产生虚函数表,那么虚基类表第一项字段的值应该是0。

通过以上的对象组织形式,编译器解决了公共虚基类的多份拷贝的问题。通过每个父类的虚基类表指针,都能找到被公共使用的虚基类的子对象的位置,并依次访问虚基类子对象的数据。至于虚基类定义的虚函数,它和其他的虚函数的访问形式相同,本例中,如果使用虚基类指针MyClass*pc访问MyClassC对象的fun,将会被转化为如下形式:

*(pc+28)[0]()

 

 

看到的关于虚函数继承及虚继承解释比较好的文章的复制

标签:

原文地址:http://www.cnblogs.com/sansanmao123/p/5747451.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!