条款32:确定你的public继承塑模出is-a关系
public继承和is-a之间的等价关系听起来颇为简单,但有时候你的直觉可能会误导你,例如企鹅是一种鸟,这是一个既定事实,但是企鹅可以飞吗?
class Bird{
public:
virtual void fly();
...
};
class Penguin:public Bird{
...
};
这下就入坑了吧!整个继承体系来说企鹅可以非,但我们知道这种绝伦是错的,那是什么情况???
这个例子中,我们就对public继承没有进行深刻了解导致被坑,我们应该承认一点:有数种鸟儿不会飞,所以public继承有问题吧!“is-a”关系并不满足!
可以为其声明一个可以使其满足“is-a”关系的基类,然后使用public继承;
class Bird{
...
};
class FlyingBird:public Bird{
public:
virtual void fly();
...
};
class Penguin:public Bird{
...
};
这个例子中我们将鸟儿分为会飞的鸟儿和不会飞的鸟儿,其中penguin属于不会飞的鸟儿!!!这种关系可以得到正确的表示了吧!
也可以为且重新定义fly函数,令其产生一个运行期错误:
void error(const std::string& msg);//定义于另外某处
class Penguin:public Bird{
public:
virtual void fly(){error("Attempt to make a penguin fly!");}
...
};
利用运行期错误可以解决掉这种“is-a”关系不匹配问题!
如果上面的例子理解的不是很深刻的话,那么我们提出另外一个例子,帮助理解“public继承其实是is-a关系”的这个概念:
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.setWidht(r.widht()+10);
assert(r.height()==oldHeight);//判断r的高度值是否未曾改变
}
class Square:public Rectangle{...};
Square s;
...
assert(s.width()==s.height());
makeBigger(s);
assert(s.width()==s.height());
结果很明显,对于Square来说,makeBigger之后,assert判断值应该仍旧为True,这下我们遇到一些问题:
- 在调用makeBigger之前,s的高度和宽度相同;
- 在makeBigger之后,s的宽度改变,但是高度不变;
- makeBigger返回之后,s的高度和宽度人就相同,注意s事宜by
reference方式传递给makeBigger,所以makeBigger修改的是s吱声,不是s的副本。
这下是不是有些小尴尬呀!哈哈,注意理解public继承实际是“is-a”关系的一种表述对于继承概念的理解将有很大的提升!
is-a并非是唯一存在与classes之间的关系,has-a和is-implemented-in-terms-of(根据某物实现出),这些关系将在后面的博客中陆续进行更新,本文只需要对public继承的is-a特性进行理解即可!
总结:
“public继承”意味着is-a,适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也是一个base class对象!