effective C++: 6.继承与面向对象设计(一)

2014-11-24 03:15:38 · 作者: · 浏览: 0
六.继承与面向对象设计


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


C++进行面向对象 编程,最重要的一个规则是:public inheritance(公有继承)意味is-a(是一种)的关系。


C++领域中,任何函数如果期望获得一个类型为基类的实参(而不管是传指针或是引用),都也愿意接受一个派生类对象(而不管是传指针或是引用)。(只对public继承才成立。)


好的接口可以防止无效的代码通过编译,因此你应该宁可采取“在编译期拒绝”的设计,而不是“运行期才侦测”的设计。
is a并不是唯一存在classes之间的关系。另两个常见的关系是has-a(有一个)和is-implemented-in-term-of(根据某物实现出)。


请记住:

“public继承”意味is-a。适用于base class身上的每一件事情一定也适用于derived class身上,因为每一个derived class对象也都是一个base class对象。




条款33:避免遮掩继承而来的名称
在继承体系下,派生类的作用域包含在基类作用域下,因而在派生类中的同名变量会遮掩基类的变量。如:
class Base{
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
...
};

class Derived : public Base{
public:
virtual void mf1();
void mf3();
void mf4();
...
};

Derived d;
intx;
...
d.mf1(); //ok,调用Derived::mf1()
d.mf1(x); //error
d.mf2(); //ok,调用Base::mf2
d.mf3(); //ok,调用Derived::mf3
d.mf3(x); //error
在基类中所有名为mf1和mf3的函数都被派生类中的同名函数遮掩掉了,即使它们有不同的参数类型,即使它们是虚函数或非虚函数。


由于上面是public继承,为了能够使用基类中的同名函数(也就是保持is-a关系),可以采用两种办法:使用using声明式或转交函数。
1.在上面的例子中,派生类可做以下修改:
class Derived : public Base{
public:
using Base::mf1; //让Base class内名为mf1和mf3的所有东西中
using Base::mf3; //在Derived作用域中可见
virtual void mf1();
void mf3();
void mf4();
...
};
Derived d;
intx;
...
d.mf1(); //ok,调用Derived::mf1()
d.mf1(x); //OK,调用Base::mf1(int)
d.mf2(); //ok,调用Base::mf2()
d.mf3(); //ok,调用Derived::mf3()
d.mf3(x); //ok,调用Base::mf3(int)
这就意味着如果继承基类并想重载基类函数,而你又希望重新定义或覆写其中一部分,那么为那些原本会被遮掩的每个名称引入以一个using声明式。


2.如果试图选择性地只继承部分重载函数,这在public继承下不可能发生,因为它违反了public继承所暗示的is-a关系,然而在private继承下可能有意义。例如上式Derived以private继承base,而Derived唯一想继承的mf1是那个无参数版本。using声明式这里没用,因为他声明的某给定名称的所有同名函数都


在Derived中可见,可以使用转交函数(forwarding function):
class Derived : private Base{ //私有继承,is-implemented-in-term-of关系
public:
virtual void mf1(){ //转交函数
Base::mf1();
}
...
};

Derived d;
intx;
...
d.mf1(); //ok,调用Derived::mf1()
d.mf1(x); //error,Base::mf1被屏蔽


请记住:
derived calsses内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。
为了让被遮掩的名称再见天日,可使用using声明式或转交函数(forwarding function)。




条款34:区分接口继承和实现继承
表面上直截了当的public继承概念,经过更严密的检查之后,发现它由两部分组成:函数接口继承和函数实现继承。成员函数的接口总是会被继承。


pure virtual函数有两个最突出的特性:它们必须被任何“继承了它们”的具象class重新声明,而且它们在抽象class中通常没有定义。所以:声明一个pure virtual函数的目的是为了让derived class只继承函数接口。
令人意外的是,我们竟然可以为pure virtual函数提供定义。但调用它的唯一途径是“调用时明确指出其class名称”:


声明简朴的(非纯)impure virtual函数的目的,是让derived class继承该函数的接口和缺省实现。
声明non-virtual函数的目的是为了令derived class继承函数的接口及一份强制性实现。
如果成员函数是个non-virtual函数,意味着它并不打算在derived classes中有不同的行为。


请记住:
接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。
pure virtual函数只具体制定接口继承。
简朴的(非纯)impure virtual函数具体制定接口继承及缺省实现继承。
non-virtual函数具体制定接口继承以及强制性实现继承。




条款35:考虑virtual函数以外的其它选择


假设你整在写一个视频游戏软件,由于不同的人物可能以不同的方式计算它们的健康指数,将healthValue声明为virtual似乎再明白不过的做法:
class GameCharacter {
public:
virtual int healthValue()const;
...
};
由于这个设计如此明显,你可能没有认真考虑其他替代方案。为了帮助你跳脱面向对象设计路上的常轨,让我们考虑其他一些解法:
1.藉由Non-virtual interface手法实现Template Method模式
有个思想流派主张virtual函数应该几乎总是private。他们建议,较好的设计是保留healthValue为public成员函数,但让它成为non-virtual,并调用一个
private virtual函数(例如doHealthValue)进行实际工作:
class GameCharacter {
public:
int healthValue() const
{
...//做一些事前工作
int retVal = doHealthV