6 继承与面向对象设计
条款32:确定你的public集成塑模出is-a关系
请记住:
public继承意味is-a,适用于base classes深证的每一件事情也一定适用于derived classes身上,因为每一个derived class对象也都是一个base class对象
条款33:避免遮掩继承而来的名称
class Base{ private: int x; public: virtual void mf1() = 0; virtual void mf2(); void mf3(); }; class Derived: public Base{ public: virtual void mf1(); void mf4(); }; void Derived::mf4(){ mf2(); }Base的作用域: mf1(1个函数);mf2(1个函数);mf3(1个函数);
Derived作用域:mf1(1个函数);mf4(1个函数);
对于mf4的实现,当编译器看到mf2,会查找各作用域,看看有没有某个名为mf2的声名式。首先查找local作用域,然后查找Derived作用域,然后查找base作用域。
看下边的例子,这次重载mf1和mf3,并添加新版mf3到Derived中去:
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(); };Base作用域:mf1(2个函数);mf2(1个函数);mf3(2个函数);
Derived作用域:mf1(1个函数);mf3(1个函数);mf4(1个函数);
从名称观点来看,Base::mf1和Base::mf2不再被Derived继承:
Derived d;
int x;
d.mf1();
d.mf1(x); //错误,Derived::mf1遮盖了Base::mf1
d.mf2();
d.mf3();
d.mf3(x); //错误,Derived::mf3遮掩了Base::mf3
如你所见,上述规则依然适用,即使函数参数不同。
这些行为背后的理由是为了防止你在程序库或应用框架内建立新的derived类时附带的从base 类继承重载函数,但有时你通常会想继承重载函数。实际上如果你正在使用的public继承而又不继承那些重载函数,就是违反了is-a关系,那么该怎么办呢?可以使用using达到目标:
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: using Base::mf1; using Base::mf3; virtual void mf1(); void mf3(); void mf4(); };
Derived作用域:mf1(2个函数);mf3(2个函数);mf4(1个函数);
此时:
Derived d;
int x;
d.mf1();
d.mf1(x); //ok,调用Base::mf1
d.mf2();
d.mf3();
d.mf3(x); //ok,调用Base::mf3
请记住:
1.derived类内的名称会遮掩base类的名称。在public继承下没有人希望如此。
2.为了让被遮掩的名称重见天日,可以使用using声明式或转交函数(forwarding functions)
条款34:区分接口继承和实现继承
作为类设计者:
1.有时希望derived类只继承成员函数的接口;
2.有时希望继承函数的接口和实现,但又希望能够override所继承的实现;
3.有时希望derived class同时继承函数的接口和实现;
class Shape{ public: virtual void draw() const = 0; virtual void error(const std::string& msg); int objectID() const; }; class Rectangle: public Shape{}; class Ellipse: public Shape{};
1.成员函数的接口总是会被继承。
Shape的三个函数类型不同,有什么样的暗示呢?
1.1 生命一个pure virtual函数的目的是为了让derived类只继承函数接口。
令人意外的是,我们竟然可以为pure virtual函数提供定义,也就是说你可以为shape::draw提供一份实现代码,C++并不会发出怨言,但调用它的唯一途径是调用时明确指出其类名:
Shape* ps = new Shape; //错误,shape是抽象的 Shape* ps1 = new Rectangle; ps1->draw(); Shape* ps2 = new Ellipse; ps2->draw(); ps1->Shape::draw(); //调用Shape::draw ps2->Shape::draw(); //调用Shape::draw1.2生命一般虚函数的目的,是让derived classes继承该函数的接口和缺省实现。
但是,允许一般虚函数同时指定函数声明和缺省行为,却有可能造成危险:
class Airport{}; class Airplane{ public: virtual void fly(const Airport& destination); //... }; void Airplane::fly(const Airport& destination){ //default code, fly to destination } class ModelA: public Airplane{}; class ModelB: public Airplane{};
class Airplane{ public: virtual void fly(const Airport& destination) = 0; //... protected: void defaultFly(const Airport& destination); }; void Airplane::defaultFly(const Airport& destination){ //default code, fly to destination }
class ModelA: public Airplane{ public: virtual void fly(const Airport& destination){ defaultFly(destination); } }; class ModelB: public Airplane{ public: virtual void fly(const Airport& destination){ defaultFly(destination); } };
(如果把fly声明为pure的,那么所有的derived类都不能随意继承fly了,所有这里这样搞一下有意义吗?)
1.3 声明non-virtual函数的目的是为了令derived class继承函数的接口及一份强制性实现。
请记住:
1.接口继承和实现继承不同。在public继承之下,derived class总是继承base class的接口。
2.pure virtual函数只具体指定接口继承。
3.non-virtual函数具体指定接口继承及缺省实现继承。
4.non-virtual函数具体指定接口继承以及强制性实现继承。