【effective c++】继承与面向对象设计
1.确定你的public继承塑造出is-a关系
public继承意味着is-a.适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived classes对象也都是一个base class对象,反过来不成立。
2.避免隐藏继承而来的名称
c++名称查找不考虑类型,只考虑名称。
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 class内所有名为mf1和mf3的函数都被derived class内的mf1和mf3函数遮掩掉 函数隐藏只与函数名称有关,与函数的参数类型、是否virtual无关,注意此处是静态绑定
若通过指针或引用来调用虚函数,此时是动态绑定,不会发生函数隐藏 */ int main() { Derived d; int x = 1; d.mf1();//ok //d.mf1(x);//false d.mf2();//ok d.mf3();//ok //d.mf3(x);//false system("pause"); return 0; }
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(){} }; /* 如果你继承base class并且base class中有重载函数,而你又希望在派生类中重新定义其中一部分,那么你必须为那些 原本会被隐藏的每个名称引入一个using声明式,否则某些你希望继承的名称会被隐藏。using声明式会令继承而来的基类中某
给定名称之所有同名函数在derived class中都可见 */ int main() { Derived d; int x = 1; d.mf1();//ok d.mf1(x);//ok d.mf2();//ok d.mf3();//ok d.mf3(x);//ok system("pause"); return 0; }
3.区分接口继承和实现继承
- 声明一个pure virtual函数的目的是为了让derived classes只继承函数接口
通常纯虚函数没有定义,但是可以为纯虚函数提供定义,调用它的唯一途径是调用时明确指出其class名称
class Shape { public: virtual void draw() const = 0{} virtual void error(const string& msg); int objectID() const; }; class Rectangle:public Shape { public: virtual void draw() const{} }; class Ellipse:public Shape { public: virtual void draw() const {} }; int main() { //Shape *ps = new Shape;//error 抽象类不能生成实例 Shape *ps1 = new Rectangle; ps1->draw(); Shape *ps2 = new Ellipse; ps2->draw(); ps1->Shape::draw(); ps2->Shape::draw(); system("pause"); return 0; }
- 声明impure virtual函数的目的,是让derived class继承该函数的接口和缺省实现
如果有的派生类只想继承接口但忘记重新定义该虚函数,这样一来就会使用基类缺省实现,出错:将接口和缺省实现分开,派生类如果想使用缺省实现,需要去显式调用
方案一:将接口定义为纯虚函数,缺省实现定义为non-virtual函数,派生类若想使用缺省实现,需要在继承而来的纯虚函数中调用该non-virtual函数,派生类若只想继承接口,此时不会忘记重新定义该虚函数了,因为不重新定义的话就是抽象类,无法实例化
方案二:纯虚函数必须在derived class中重新定义,但基类中也可以为纯虚函数提供定义,该定义作为缺省实现。派生类若想使用缺省实现,需要在在继承而来的纯虚函数中通过基类名来显式调用上述函数定义
- 声明non-virtual函数的目的是为了令derived class继承函数的接口及一份强制性实现
4.考虑virtual函数以外的其他选择
- NVI手法:令用户通过public non-virtual成员函数间接调用private virtual函数
- 由Function Pointer实现strategy模式
运用函数指针(作为类的成员变量)替换virtual函数,优点是每个对象可各自拥有自己的对应函数及可在运行期改变该函数,缺点是可能会降低类的封装性
- 由tr1::function完成strategy模式
- 古典的strategy模式
5.绝不重新定义继承而来的non-virtual函数(设计理念)
6.绝不重新定义继承而来的缺省参数值
虚函数是动态绑定的,而缺省参数值是静态绑定的。
class Shape { public: enum ShapeColor{Red,Green,Blue}; virtual void draw(ShapeColor color = Red) const = 0; }; class Circle :public Shape { public: /* 当用户以对象调用此函数,一定要指定参数值。因为静态绑定下这个函数并不从其基类继承缺省 参数值。但若以指针或引用调用此函数,可以不指定参数值,因为动态绑定下这个函数会从其基 类继承缺省参数值 */ virtual void draw(ShapeColor color) const; };
7.通过复合composition塑造出has-a或"根据某物实现出"
复合:某种类型的对象内含别的类型的对象,复合意味着has-a或is-implemented-in-terms-of
8.明智地使用private继承
- 如果class之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象(将派生类实参传递给基类形参,编译出错)
- 由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们在base class中原本是protected或public属性,即派生类对象不能调用基类方法
private继承意味着implemented-in-terms-of,private继承纯粹只是一种实现技术。
9.明智地使用多重继承
未完待续