加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

[转载]C++第一部分学习:继承(四)

(2012-06-14 16:41:27)
标签:

转载

分类: 计算机相关

隐藏带来的访问错误

class Base   {

         public:

                   int doit( double );

};

class Derived : public Base   

{

    private:

             int doit( int );  // 私有的同名函数,尽管形参不同

};

Derived  d;

int a = d.doit(12.6);  // 错误!

     不能访问Derived中私有的成员函数”

分析:

d 对象是Derived类的,于是编译器将定位于该作用域,此时,尽管由于全盘接受使子类作用域有两个doit,但由于同名,使父类公有那个doit被隐藏,可用的只有int doit( int ).而这个函数是私有的,因此,调用错误.

该隐藏机制甚至可以不必拘泥于一个函数名。子类的同名数据成员足以隐藏父类的同名成员

不要用存取权限分辨同名二义

class Base1

{

         public:

                   int doit();

};

class Base2

{

         private:

                   int doit();

};

class Derived : public Base1 ,public Base2

{   ...    };

Derived  d;

int a = d.doit();

虽然一个是private,一个是public,但是存取权限不具有多态性!

错误!

编译器将此调用视为模棱两可。

只能写为:

d.Base1:: doit();

d.Base2:: doit();

路径二义性问题

class B  {

  public:

       void fun();

       int b;

};

class B1 : public B {

  private:

       int b1;

};

class B2 : public B {

  private:

       int b2;

};

class D : public B1,public B2

{

  public:

      int fund();

  private:

      int d;

};

void  main()

{

       D   dobj ;

}

子类D对象的存储结构示意图:

[转载]C++第一部分学习:继承(四)

路径二义的含义:

         D  dobj;

         dobj.b

         dobj.B::b

类间继承关系组成了一个菱形,到底是哪个b

固然可以用类名::加以区别,  obj.B1::b obj.B2::b却表示的是两个不同的成员,这不合道理。 这种现象造成了一个子类中有多套基类成员。

虚基类

l  虚基类的引入

  用于有共同基类的多继承场合(多层共祖)

l  声明

  virtual修饰说明共同的直接基类
例:class B1: virtual  public B

l  作用

  用来解决多继承时可能发生的对同一基类继承多次和多层而产生的二义性问题.

  为最远的派生类提供唯一的基类成员,而不产生多个重复的副本。

l  注意:

  在第一级继承时就要将共同基类设计为虚基类。

虚基类举例

class B  { public: int b;};

class B1 : virtual public B { private: int b1;};

class B2 : virtual public B { private: int b2;};

class D: public B1, public B2{ private: float d;};

在子类对象中,最远基类成分是唯一的。于是下面的访问是正确的:

D  dobj;

dobj.b;

虚基类派生的对象存储结构示意图:

[转载]C++第一部分学习:继承(四)

[转载]C++第一部分学习:继承(四)



 

 

真正的实现机制

l  虚拟继承时,会在派生类的对象中自动添加一个vbptr指针,这个vbptr指向一个类维护的、全体对象共享的vbtable(偏移量表),是个数组,(请类比虚函数的“虚函数表”vtable)

l  这个表的第一项为:某类的vbptr相对于该类对象的首地址的偏移量 (通常为0) 。简单说就是安插进来的vbptr在对象的什么位置上。

l  这个表的第二项为:在派生类中所含虚基类对象部分的首地址与该虚基类的vbptr之间的偏移量。

l  每一个被虚拟继承的类都有且仅有自己的一个表,子类虚拟继承了几个基类,就会有几个vbptr,就会有几个偏移量表。

l  Vbtable在内存的静态区,相当于静态成员。以上过程是构造函数实现的。

l  增加指针这样看来是使得对象膨胀了,但是当父类中含有很大的成员,比如一个类对象时,这种方法就会非常省空间。

有同学问:既然两个父类中有两套祖父类的成员,随便找一个去掉就行了啊。要注意,子类继承了两个父类,就应该保证子类对象中包含完整的两个父类的成员,若擅自去掉其中一个,则造成子类中的父类部分不完整。

#include <iostream>

using namespace std;

class B0   //声明基类B0

{ public:   //外部接口

         int nV;

         void fun(){cout<<"Member of B0"<<endl;}

};

class B1: virtual public B0  //B0为虚基类,派生B1

{ public:   //新增外部接口

         int nV1;

};

class B2: virtual public B0  //B0为虚基类,派生B2

{  public:        //新增外部接口

         int nV2;

};

class D1: public B1, public B2         //派生类D1声明

{

 public:   //新增外部接口

           int nVd;

           void fund(){cout<<"Member of D1"<<endl;}

};

void main()      //程序主函数

{

      D1 d1;           // 声明D1类对象d1

           d1.nV=2;      // 使用最远基类成员

           d1.fun();

}

编译器自动为指对象插入了“内部调整指针”,这不是“指向成员的针”,而是指向“调整量数组”的指针。因为基类B0会含有多个成员,这些成员都要被调整走,不可能用一个指针指向多个成员,因此需要一个“调整量数组”

这个“内部调整指针”简称其为“指针”,它占4个字节(一个字长),当然它仍然遵循对齐原则。

这个“内部调整指针”不是只出现在孙类对象中,在两个子类的对象中就已存在了(若产生对象的话才能看到)。

可以看出,编译器外加的“内部调整指针”和“调整量数组”都是用于对象数据成员的,与函数成员无关。函数在继承时是“共享的”而不是“含有”,不占用对象空间,也就不用调整。

虚基类再例

class B  { };

class B1 : virtual public B { };

class B2 : virtual public B { };

class D: public B1, public B2{  };

现在对各类测试大小:

 sizeof ( B );

 sizeof ( B1 );

 sizeof ( B2 );

 sizeof ( D );

你认为应该是几?大概很意外!

1   

4

4

8

算类对象大小时,不用考虑成员函数,他们是共享的,不占用数据空间。如果一个类是空类,因为要保证new一个这个类的对象时,不出现大小为0的违反原理的情况,编译器默认空类对象占了1个大小的位置。而虚拟继承了空类的空类因为隐含了一个调整量指针,所以大小为4D中含有了两个调整量指针,所以大小为8

 

作者声明:本文中使用的ppt内容、图片内容版权归东软侯克林老师所有,转载请注明出处,不得用于商业目的

0

  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有