游戏开发论坛

 找回密码
 立即注册
搜索
查看: 10769|回复: 3

C++基本功和 Design Pattern系列(6) public,protect,private inheritance

[复制链接]

27

主题

179

帖子

259

积分

中级会员

Rank: 3Rank: 3

积分
259
发表于 2006-11-25 03:04:00 | 显示全部楼层 |阅读模式
======================================================
大家请把我的文章当参考,详细内容  还请参照 权威书籍
<c++ programming language>如果文中有错误和遗漏,
请指出,Aear会尽力更正, 谢谢!
Aear Blog: http://blog.sina.com.cn/u/1261532101
======================================================

今天讲的是 public inheritance, protected inheritance & private inheritance,内容不多,但是非常重要。基本的类的继承,也就是inheritance的概念大家都清楚,明确的定义不再详细说明了。先面举个例子来说明:

class People {
    ...
    Walk();
    Eat();
};

class Student : public People{
   ...
   Study();
};

注意这行:
    class Student : public People {
中的public,表明是public inheritance,如果换成protected,就是protected inheritance, private就是private inhertance. 首先需要说明的是3种inheritance在语法上相似,但是在语意上完全不同。我们先从public inheritance说起。

=====================public inheritance=====================

public inheritance最基本的概念就是"isa" ( is a )。简单的说,继承类也就是Derived Class "is a" Base Class. 用上面的例子来说,People是base class, Student是 derived class,所以能够推导出: “student is a people” 这句话。如果你无法推导出 "isa"的关系,那么就不应该使用public inheritance.

其次,即使是能推导出 "isa" 的关系,也必须满足2个条件,才能使用 public inheritance. 这2个条件是:

    1. 所有Base Class的属性,也就是 attribute,Derived Class都有。
    2. 所有Base Class的方法,Derived Class都应该包含。

在上面的例子中,student也是个people,所以能够Walk() 和 Eat(),因此public inheritance 是合理的。
如果满足 "isa" 但是不满足上述条件,建议使用 Delegation/Composition,具体关于Delegation和Composition,在"C++基本功和 Design Pattern系列(1)" 中有说明。让我们看下在《Effective C++》中的一个例子来说明这种情况:

class Rectangle {
    ...
    SetWidth();
    SetHeight();
};

class Square : public Rectangle {
    ...
    SetLength();
};

我们大家都知道,一个正方形Square,一定是一个长方形Rectangle,所以满足"isa"的条件。我们给Rectangle提供了SetWidth()和SetHeight()的方法。如果不考虑上面2条,只考虑 "isa",那么这个 public inheritance是合理的,但是让我们看看会出现什么问题。

在Square中我们要求长和宽必须相等,因此我们提供了SetLength(),来同时设置正方形的长和宽。但是有一位Bill小朋友无法分辨长方形和正方形,因此写出了如下代码:

    Square MySquare;
    MySquare.SetWidth(100);
    MySquare.SetLenght(200);

那么问题出现了,MySquare并不是一个Square。相信大家都明白了吧。语言的不精确性导致在设计过程中出现的错误是屡见不鲜的。因此,在 public inheritance的时候要特别注意。也许有人会说,我们把SetHeight 和 SetWidth设置成Virtual然后在Square Class中重载不就可以了吗?如果Rectangle和Square 2个class都是你来写,那么也许不会出现问题。但是如果一个非常复杂的class,包含几十个方法和几十个属性,并且由别人来写,那么你会不会仔细的阅读代码并且overlord每一个需要的方法呢?即使你这样做了,也许会带来更多的麻烦。因为有可能破坏内部数据的一致性。

让我们来看看interface inheritance的例子:

    Class Bird {
       ...
       virtual Fly() = 0;
    };

    Class Turkey : public Bird {
       ...
       Fly() { cout << "I cannt fly! Jessus....." <<endl; };
    };

    Turkey Bird0;
    ...
    Bird0.Flg();   // runtime error

首先,鸟能飞,这个没有问题,火鸡是一种鸟,这也没有问题,但是:火鸡不能飞。问题出现了,client能够调用Turkey的Fly()方法,但是得到的确是一个 RunTime Error! 这里必须强调下:"RUNTIME ERROR!",对于游戏程序来说,一个"RUNTIME ERROR"基本上就等于程序崩溃。和out of memory同等性质。如果你玩WOW做7个小时中间不能间断的任务,然后出现一只火鸡给个RUNTIME ERROR....我想是人都会崩溃吧。

所以对于这种错误,我们要在编译的时候尽量查出来,也就是 Prefer Compile Error over Runtime Error. 通过更改类的设计,我们可以避免类似的runtime error:
    Class Bird {
       ...
    };
  
    Class UnflyableBird : public Bird{
       ...
       // no fly() here
    };

    Class Turkey : public UnflyableBird {
       ...
    };

    Turkey Bird0;
    ...
    Bird0.Flg();   // compile error....
  
所以,要想使用public inheritance,必须满足:

    1. "ISA"
    2. 所有Base Class的属性,也就是 attribute,Derived Class都有。
    3. 所有Base Class的方法,Derived Class都应该包含。


=====================private inheritance=====================

private inheritance和public inheritance最大的区别就在于,private inheritance不满足"isa"的关系。举个例子:

class People {
    ...
    Walk();
    Eat();
};

class ET: private People{
   ...
};

外星人ET是一种类似人的生物,能做一些类似人的动作,但是并不是人。从C++的语法上面来讲,下面的代码是错误的:

    People*  p = new ET();   // ERROR, ET is not a People

使用private inheritance的目的只是简单的为了代码重用。因此如果不满足public inheritance的条件,可以使用 Delegation/composition 和 Private Inheritance。 那么在什么情况下使用 private inheritance,什么情况下使用Delegation/Composition 呢?

有2种情况是推荐使用 private inheritance的,其他的情况下,推荐使用Delegation/Composition.

情况1: 需要对Base Class中的 private/protect virtual 进行重载。比如类似Draw() 等等。

情况2: 不希望一个Base class被 client使用。

关于情况2,举个简单的例子:
如果我们不希望Base Class被别人直接使用,有2种方法,第一是:把它设置成为abstract class, 也就是包含pure virtual function. 第2种方法是把constructor 和 descturctor设置成 protected.代码如下:

class Base {
protected:
   Base();
   virtual ~Base();
};

class Derived : private Base {
    ...
};

Base n; // Error, Base() cannot be called
Derived m; // ok, Derived can call Base()

这样我们又可以保证n的代码可以被m使用,又可以防止 client直接调用 Base进行我们不希望的操作。

=====================protected inheritance=====================

protected inheritance和 private inheritance没有本质的区别,但是如果我们希望的 Derived Class 能够作为其他 class的基类,那么就应该使用 protected inheritance.

今天就说这么多,有空来我的Blog做客:http://blog.sina.com.cn/u/1261532101 ,下次见!

8

主题

553

帖子

560

积分

高级会员

Rank: 4

积分
560
发表于 2006-11-25 08:54:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(6) public,protect,private inheritance

不错,马马赶快出来指出 有那些地方值得改进。

1

主题

18

帖子

18

积分

新手上路

Rank: 1

积分
18
发表于 2006-11-26 15:08:00 | 显示全部楼层

Re: C++基本功和 Design Pattern系列(6) public,protect,private inheritance

提个建议,除了文中已有的形象的例子外,能不能举几个在实际开发中常常用到的典型代码?
  [em24]

8

主题

716

帖子

716

积分

高级会员

Rank: 4

积分
716
发表于 2006-11-27 18:41:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(6) public,protect,private inheritance

引自原来在CGD讨论的几点总结:
1. public继承被称为类型继承(type inheritance)。往往反映is-a关系。
2. protected继承基类的所有公有成员都成为派生类的protected成员。这意味着它们可以被后来从该类派生的类访问,但不能在层次结构之外被访问。
3. private继承被称为实现继承(implementation inheritance)。派生类提供自己的公有接口,重用基类的实现。

数据成员的访问域变化:
1. public继承的话,原来父类的public ,protected成员继承到子类中,类型不变员
2. protected继承的话,原来父类的public ,protected成员继承到子类中,并成为protected类型成员
3. private继承的话,原来父类的public ,protected成员继承到子类中,并成为private类型成员

PS一下
理想情况下,是能不用public就不用,因为一旦public就引发一系列如client可以用base pointer去new inherit class,则dtor需要是virtual的问题
但大多数情况下都是public的情况,大家对此也比较熟悉,所以下面谈谈不常见的情况

在private inheritance时,如果不存在必须实现virtual method的情况下,推荐选择composition,避免了未来可能的multi inheritance导致出现的讨厌的菱形结构从而引发出现的C++阴暗面: virtual inheritance,即不要因为只是想少敲点代码而失去了良好的设计

protected和private区别在于:如果只是为了重用code,在不能composition的情况下则优先考虑private,除非派生类还会需要或是有可能会被其他类继承的话,才需要变成protected。


再BTW一下
就像是C++他爹也会推荐C++饭在平时也应该多接触一些新的动态语言,像是python/ruby/lua。在强类型环境下生活惯了的人,尝试一下弱类型,强弱搭配,会更容易设计出一些平时所不能实现的设计。
另外,虽然C++博大精深,但大家不要忘了还有design by contract,在一定的协议范围内开发,就不容易遇到设计时寸步难行的地步。本贴提到的继承,最好的反例就是<functional>中的std::binary_function及其众多派生类std::plus、std::minus等(大家可以想一下为什么这里举这个例子)。所以C++并不是要求我们完全得死读书读死书,灵活和多种解决方案是她的特点,我们只需要明白自己是在做什么,并且将风险性和未来扩展性全面得考虑进去。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2026-1-25 23:21

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表