继承与 Data Member(1)

在 C++ 继承模型中, 一个derived class object 所表现出来的东西, 是其自己的的 members 加上其 base class members 的总和。 至于 derived class members 和 base class members 的排列次序并未在 C++ Standard 中强制指定; 理论上编译器可以自由安排之, 就是说是个 multi-unordered-set(我自己编的名词)。 在大部分编译器上头, base class members 总是先出现, 但属于 virtual base class 的例外(反正就是, 任何一个规则一旦碰到 virtual base class 就有例外, 就是这么拧巴)。
然后可能就有人问了, 假如我为 2D 或 3D 坐标点提供两个抽象数据类型如下:

//supporting abstract data types
class Point2d
{
public:
    //constructors
    //operations
    //access functinos
private:
    float _x, _y;
};

class Point3d
{
public:
    //constructors
    //opsrations
    //access functions
private:
    float _x, _y, _z;
};

这样的数据结构和层层派生, 每个维度都派生自较低维有什么区别? 所以以下内容将探讨以下情况:
1. 单一继承且不含 virtual functions
2. 单一继承并含 virtual functions
3. 多重继承
4. 虚拟继承
以上四种情况, 下图就是在没有 virtualfunctions 的情况下,它们和 C struct 的区别(就是没区别, 完全一样)

只继承不多态

想象一下, 程序员可能希望, 无论是 2D 还是 3D 坐标点, 都能够共享一个实体, 但又能继续使用与类型性质相关的实体。我们有一个设计策略,就是从 Point2d 派生出一个 Point3d, 于是 Point3d 将继承 _x 和 _y 坐标的一切, 带来的影响则是可以共享数据核对数据的处理方法, 并将其局部化。 一般而言, 具体继承(concrete inheritance 相对于 虚拟继承 virtual 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 x){_x = x;}
    void Y(float y){_y = y;}

    void operator+=(const Point2d &rhs)
    {
        _x += rhs.X();
        _y ++ rhs.Y();
    }
    //more operations
protected:
    float _x, _y;
};

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;}
    float Z(float z){_z = z;}
    
    void operator+=(const Point3d &rhs)
    {
        Point2d::operator+=(rhs);
        _z += rhs.Z();
    }
    //more operations
protected:
    float _z;
};

这样设计的好处就是可以把 x 和 y 坐标的程序代码局部化, 以及可以更好的表示两个类之间的逻辑关系, 当这两个泪独立的时候, Pointd 和 Point3d 的声明和使用都不会有所改变, 所以这两个抽象的使用者不需要知道是否这个 object 是独立的 classes 类型, 或是彼此之间有继承的关系, 下图就显示了 Point2d 与 Point3d 之间继承关系的实物布局, 其间并没有 virtual 接口(论述的这个情况没有多态):

那么这么做又有什么坏处呢?
1. 经验不足的人可能会涉及一些相同操作的函数。 以上个例子中的 constructor 和 operator 为例, 他们并没有被做成 inline 函数(当然也可能是编译器为了某些里有没有支持 inline member functions)。 Point3d object 的初始化操作或加法操作将需要部分的 Pointd object 和部分的 Point3d object 作为成本。 一般而言, 选择某些函数做成 inline 函数, 是设计 class 时的一个重要课题。
2. 把一个 class 分解为两层或更多层, 有可能为了表现 class 体系的抽象化而膨胀所需空间。C++ 语言保证出现在 derived class 中的 base class subobject 有其完整原样性, 这正是重点所在。举个例子, 一个 class 只含有一个 int 成员变量和三个 char 成员变量, 它的大小应是 8 bytes, 但是一旦设计成上述的分层结构, 同样多的成员, 却要占 16 bytes 的空间, 是原先的两倍! 其原因可用下图解释:

而更可怕的结果是地址的覆盖, 比如我声明一组指针:
Layer2 *p2;
Layer1 *p1_1, *p1_2;
其中 p1_1 和 p1_2 都可以指向上述三种 classes objects, 而以下的的这个操作:
*p1_2 = *p1_1;
应该执行一个 memberwise 的复制操作, 对象则是被指的 object 的 Layer1 那部分, 那如果有以下操作:
p1_1 = p2;
//到这一步时就错了, 因为 derived subobject 会被覆盖掉
*p1_2 = *p1_1; //p1_2 的对象现在有了一个不确定的数值
下图可解释发生的事情:

 

欲知更多请接着看下一篇

posted @ 2014-11-19 17:28  wu_overflow  阅读(258)  评论(0编辑  收藏  举报