码迷,mamicode.com
首页 > 编程语言 > 详细

C++对象模型——"继承"与Data Member(第三章) .

时间:2015-08-05 22:18:58      阅读:215      评论:0      收藏:0      [点我收藏+]

标签:

3.4 "继承"与Data Member
 在C++继承模型中,一个derived class object所表现出来的东西,是其自己的members加上其base class members的总和.至于derived class members和base class members的排列次序并未在C++ Standard中强制指定:理论上编译器可以自由安排.在大部分编译器上,base class members总是先出现,但属于 virtual base class的除外.
 了解这种继承模型之后,那么如果为2D或者3D坐标点提供两个抽象模型如下:
// support abstract data types
class Point2d {
public:
 // constructor
 // operations
 // access functions
private:
 float x, y;
};
class Point3d {
public:
 // constructor
 // operations
 // access functions
private:
 float x, y, z;
};
 这和"提供两层或三层继承结构,每一层(代表一个维度)是一个class,派生自较低维层次"有什么不同?
 下面各小节的讨论将涵盖"单一继承且不含virtual functions","单一继承并含virtual functions","多重继承","虚拟继承"等四种情况.

只要继承不要多态
 想象一下,程序员或许希望,不论是2D或3D坐标点,都能够共享同一个实体,但又能够继续使用"与类型性质相关(所谓type-specific)"的实体.有一个设计策略,就是从Point2d派生出一个Point3d,于是Point3d将继承x和y坐标的一切(包括数据实体和操作方法),带来的影响则是可以共享"数据本身"以及"数据的处理方法",并将其局部化.一般而言,具体继承(concrete inheritance)并不会增加空间或存取时间上的额外负担.
class Point2d {
public:
 Point2d(float x = 0.0, float y = 0.0) : _x(x), _y(y)  {}
 float x() { return _x; }
 float y() { return _y; }
 void x(float newX) { _x = newX; }
 void y(float newY) { _y = newY; }
 
 void operator+=(const Point2d &rhs) {
  _x += rhs.x();
  _y += rhs.y();
 }
protected:
 float _x, _y;
};
// inheritance from concrete class
class Point3d : public Point2d {
public:
 Point3d(float x = 0.0, float y = 0.0, float z = 0.0)
  : Point2d(x, y), _z(z) {}
 
 float z() { return z; }
 void z(float newZ) { _z = newZ; }

 void operator+=(const Point3d &rhs) {
  Point2d::operator+=(rhs);
  _z += rhs.z();
 }
protected:
 float _z;
};
 这样设计的好处就是可以把管理x和y坐标的程序代码局部化,此外这个设计明显表现出两个抽象类之间的紧密关系.当这两个 class 独立的时候,Point2d object和Point3d object的声明和使用都不会有所改变.所以这两个抽象类的使用者不需要知道objects是否为独立的 class 类型,或是彼此之间有继承的关系.
 把原本两个独立不相干的 class 凑成一对"type/subtype",并带有继承关系,会有什么易犯的错误呢?经验不足的人可能会重复设计一些相同操作的函数.以例子中的constructor和operator+=为例,它们并没有被做成 inline 函数.Point3d object的初始化操作或加法操作,将需要部分的Point2d object和部分的Point3d object作为成本.第二个易犯的错误是,把一个 class 分解为两层或更多层,有可能为了"表现class体系的抽象化"而膨胀所需空间.C++语言保证"出现在derived class中的base class subobject有其完整原样性".正是重点所在,如下:
class Concrete {
public:
 // ...
private:
 int val;
 char c1;
 char c2;
 char c3;
};
 在一个32位机器中,每一个concrete class object的大小都是8 bytes,细分如下:
 1. val占用4 bytes
 2. clc,c2和c3各占用1 bytes
 3. alignment(调整到 void 边界)需要1 bytes
 现在假设,经过某些分析后,决定了一个更逻辑的表达方式,把concrete分裂为三层结构:
class Concrete1 {
public:
 // ...
private:
 int val;
 char bit1;
};
class Concrete2 : public Concrete1 {
public:
 // ...
private:
 char bit2;
};
class Concrete3 : public Concrete2 {
public:
 // ...
private:
 char bit3;
};
 从设计的观点来看,这个结构可能合理,但从效率的观点来看,可能受困于一个事实:现在Concrete3 object的大小是16 bytes,比原先的设计多一倍.
 怎么回事,之前提到过"base class subobject在derived class中的原样性":
 Concrete1内含两个members:val和bit1,加起来是5 bytes.而一个Concrete1 object实际用掉8 bytes,包括填充用的3 bytes,以使object符合一部机器的word边界.不论是C或C++都是这样.一般而言,边界调整(alignment)是由处理器(processor)来决定.
 Concrete2加了唯一一个nonstatic data member bit2,数据类型为char.轻率的程序员以为它会和Concrete1捆绑在一起,占用原本用来填充空间的1 bytes;于是Concreate2 object的大小为8 bytes,其中2 bytes用于填补空间.
 然而Concrete2的bit2实际上却是被放在填补空间所用的3 bytes之后,于是其大小变成12 bytes,不是8 bytes.其中的6 bytes浪费在填补空间上.同样的道理使得Concrete3 object的大小是16 bytes,其中9 bytes用于填补空间.
 声明以下指针:
Concrete2 *pc2;
Concrete1 *pc1_1, *pc1_2;
 其中pc1_1和pc1_2两者都可以指向前述三种 class object,下面这个指定操作:
*pc1_2 = *pc1_1;
 应该执行一个默认的"memberwise"复制操作(复制一个个的members),对象是被指的object的Concrete1那一部分.如果pc1_1实际指向一个Concrete2 object或Concrete3 object,则上述操作应该将复制内容指定给其Concrete1 subobject.
 然而,如果C++语言把derived class member(也就是Concrete2::bit2或Concrete3:;bit3)和Concrete1 subobject捆绑在一起,去除填补空间.上述那些语意就无法保留,那么下面的指定操作:
pc1_1 = pc;   // 令pc1_1指向Concrete2对象
*pc1_2 = *pc1_1; // derived class subobject被覆盖掉
 就会将"被捆绑在一起,继承而得的"members内容覆盖掉,程序员必须花费极大的心力才能找出这个bug.

版权声明:本文为博主原创文章,未经博主允许不得转载。

C++对象模型——"继承"与Data Member(第三章) .

标签:

原文地址:http://blog.csdn.net/yiranant/article/details/47304279

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