Effective C++——条款32(第6章)

第6章   继承与面向对象设计

Inheritance and Objected-Oriented Design

    这一章节关于C++的面向对象编程OOP,讨论以下问题:继承可以是单一继承或多重继承,每一个继承连接可以是 public,protected,private,也可以是 virtual 或 non-virtual .然后是成员函数的选项:virtual?non-virtual?pure virtual?Y以及成员函数和其他语言特性的交互影响:缺省参数值与 virtual 函数有什么交互影响?继承如何影响C++的名称查找规则?设计选项有哪些?如果 class 的行为需要修改,virtual 函数是最佳选择吗?
    public 继承意味着"is-a",如果尝试让它带有其他意义,则是错误的.同样的道理,virtual 函数意味"接口必须被继承",non-virtual 函数意味着"接口和实现都必须被继承".

条款32: 确定你的 public 继承塑模出is-a关系

Make sure public inheritance models "is-a"

    以C++进行面向对象编程,最重要的一个规则是:public inheritance意味着"is-a"的关系.
    如果令 class D("Derived")以 public 形式继承 class B("Base"),便是告诉C++编译器说,每一个类型为D的对象同时也是一个类型为B的对象.意思是B比D表现出更一般化的概念,而D比B表现出更特殊化的概念.

    C++对于"public继承"严格奉行上述见解,考虑以下例子:
class Person { ... };
class Student : public Person { ... };
    根据生活经验知道,每个学生都是人,但并非每个人都是学生.这便是这个继承体系的主张.
    如上所述,在C++领域中,任何函数如果期望获得一个类型为Person(或pointer-to-Person或reference-to-Person)的实参,都也愿意接受一个Student对象(或pointer-to-Student或
reference-to-Student):
void eat(const Person& p);
void study(const Student& s);
Person p;
Student s;
eat(p);         // ok
eat(s);         // ok
study(s);       // ok
study(p);       // error!
    这个论点只对 public 继承才成立.只有当Student以 public 形式继承Person,C++的行为才如上所述. private 继承的意义与此完全不同(详见条款39).
    public 继承和"is-a"之间的等价关系看起来很简单,但有时候直觉会误导人.例如企鹅是一种鸟,鸟可以飞,这也是事实.如果天真地以C++描述这层关系,如下:
class Bird {
public:
    virtual void fly();     // bird can fly
};
class Penguin : public Bird {
    ...
};
    突然间混乱,因为这个继承体系说企鹅可以飞!!!怎么回事?
    这是由于语言的不严谨造成的,鸟可以飞,其实是说大多数鸟可以飞.因此也有一小部分鸟不会飞,因此以下继承关系才是准确的:
class Bird {
    ...             // 没有声明fly函数
};
class FlyingBird : public Bird {
public:
    virtual void fly();
};
class Penguin : public Bird {
    ...             // 没有声明fly函数
};
    这反映出一个事实,并不存在一个"适用于所有软件"的完美设计.所谓 最佳设计,取决于系统希望做什么事,包括现在与未来.
    另外一种思想派别处理所谓"所有的鸟都会飞,企鹅是鸟,但是企鹅不会飞"的问题,就是为企鹅重新定义fly函数,令它产生一个运行期间错误:
void error(const std::string msg);          // 定义于另外某处
class Penguin : public Bird {
public:
    virtual void fly() { error("Attempt to make a penguin fly!"); }
    ...
};
    很重要的是,必须认知这里所说的某些东西可能和所想的不同,这里并不是说"企鹅不会飞",而是说"企鹅会飞,但尝试那么做是一种错误".
    如何描述期间的差异? 从错误被侦测出来的时间点来看,"企鹅不会飞"这一限制可由编译器强制实施, 但若违反"企鹅尝试飞行,是一种错误"这一条规则,只有运行期间才能检测出来.
    为了表现"企鹅不会飞"的限制,就不可以为Penguin定义fly函数.
    这和采取"令程序于运行期发生错误"的方法极为不同,若以那种做法,编译器不会对p.fly调用形式发出任何警告. 条款18所述:好的接口可以防止无效的代码通过编译, 因此应该宁可采取"在编译期拒绝企鹅飞行"的设计,而不是"只在运行期才能侦测它们"的设计.
    接下来考虑一个几何学的问题,正方形和矩形之间的关系. class Square是否应该以 public 形式继承 class Rectangle?
    "当然如此,每一个正方形都是矩形,矩形不一定是正方形",这是真理.但在实际继承体系中...,考虑以下代码:
class Rectangle {
public:
    virtual void setHeight(int newHeight);
    virtual void setWidth(int newWidth);
    virtual int height() const;
    virtual int width() const;
    ...
};
void makeBigger(Rectangle& r) {
    int oldHeight = r.height;
    r.setWidth(r.width() + 10);
    assert(r.height() == oldHeight);    // 判断r的高度是否未曾改变
}
    显然,上述的assert结果永远为真,因为makeBigger只改变r的宽带,r的高度未被改变.
    现在考虑这段代码,其中使用 public 继承,允许正方形被视为一种矩形:
class Square : public Rectangle { ... };
Square s;
...
assert(s.width() == s.height());
makeBigger(s);
assert(s.width() == s.height());
    对于正方形而言,这两个assert都是真的,因为它的高度和宽度一定相等.但这两个assert对于矩形而已,不可能都为真.但是正方形是继承矩形的,两者结果应该一致.这竟然发生了矛盾.
    因此,public 继承和平时的认知是有不同的.对于一些继承而言是可能接近事实真相的,但也可能不.
    is-a并非是唯一存在于 class 之间的关系.另两个常见的关系是has-a(有一个)和is-implemented-in-terms-of(根据某物实现出),详见条款38和条款39.
    注意:
    "public继承"意味is-a.适用于base class 身上的每一件事情一定也适用于derived class 身上,因为每一个derived class 对象也都是一个base class 对象.

你可能感兴趣的:(Effective,C++,C++)