Effective C++笔记(8)—继承与面向对象设计
来源:互联网 发布:网上商城源码 java 编辑:程序博客网 时间:2024/06/02 04:46
条款32:确定你的public继承塑模出is-a关系
以C++进行面向对象编程,最重要的一个规则是:public inheritance(公开继承)意味着”is-a”关系。
class Person{};class Student:public Person{};
表示Student is-a Person
但是,公有继承is-a关系,需要注意对接口的设计。
书中的例子:
class Bird{};class Penguin:public Bird{};
Penguin is-a Bird 但是企鹅不会飞。
再比如:
class Rectangle{};class Square : public Rectangle{};
我们知道正方形是矩形,但在设计时,很可能在Rectangle类中有一个makeBigger
函数,他会修改矩形的长或者宽,当它被继承到Square中后,显然是不合理的。
条款33:避免遮掩继承而来的名称
int main(){ int x = 10; { double x = 5.5; cout << x << endl;//输出5.5 } return 0;}
到类继承关系中,我的理解编译器遵循就近原则,比如上述例子中,先找到local再到全局,全局的同名变量就被local遮盖了。
class Base{private: int x;public: virtual void mf1() = 0; virtual void mf1(int a){ cout << "base::mf1::int" << endl; } virtual void mf2() { cout << "Base::mf2" << endl; } void mf3(){ cout << "base::mf3" << endl; } void mf3(double a){ cout << "base::mf3::double" << endl; }};class Derived :public Base{public: virtual void mf1(){ cout << "derived::mf1()"<<endl; } void mf3(){ cout << "derived::mf3" << endl; } void mf4(){ mf2(); }};int main(){ Derived d; d.mf1();// ok derived::mf1() //d.mf1(2);//error 继承类中的mf1遮盖了Base d.mf2();//ok public继承直接调用base base::mf2 d.mf3();//ok 调用derived版本 base被遮盖 derived::mf3 //d.mf3(2);//error 理由同上 getchar(); return 0;}
使用using可以让Derived看见base里面同名的内容,而不至于遮盖:
class Derived :public Base{public: using Base::mf1; using Base::mf3; virtual void mf1(){ cout << "derived::mf1()"<<endl; } void mf3(){ cout << "derived::mf3" << endl; } void mf4(){ mf2(); }};int main(){ Derived d; d.mf1();// ok derived::mf1() d.mf1(2);//现在ok了 base::mf1::int d.mf2();//ok public继承直接调用base base::mf2 d.mf3();//ok 调用derived版本 base被遮盖 derived::mf3 d.mf3(2);//现在ok了 base::mf3::double getchar(); return 0;}
条款34:区分接口继承和实现继承
本条款收获的几个知识:
1.纯虚函数也可以有实现,如果给虚基类的析构函数定义成纯虚函数,则必须要给一个实现,否则会出现链接错误。
2.在多态环境下,函数和接口的继承有3种情况:
纯虚函数:要求派生类必须实现自己的版本,即只继承接口
虚函数:派生类可以有自己的实现版本,既继承了接口,又继承了默认版本(基类版本)
普通成员函数:接口继承和强制性的实现继承,不具备运行时类型检查,会调用基类版本。
公有继承之下,派生类总是继承基类的接口。
#include <iostream>#include <string>using namespace std;class Shape{public: virtual void draw() = 0{cout << "shape::draw" << endl;} virtual void error(){ cout << "Shape::error" << endl; } void objectID(){ cout << "Shape::ObjectID" << endl; } virtual ~Shape() = 0{}};class Rectangle : public Shape{public: virtual void draw(){cout << "Rectangle::draw" << endl;} //virtual void error(){ cout << "Rectangle::error" << endl; } void objectID(){ cout << "Rectangle::ObjectID" << endl; }};class Ellipse : public Shape{public: virtual void draw(){ cout << "Ellipse::draw" << endl;} virtual void error(){ cout << "Ellipse::error" << endl; } void objectID(){ cout << "Ellipse::ObjectID" << endl; }};int main(){ //Shape *ps = new Shape;//error Shape是抽象类类型 Shape* ps1 = new Rectangle; ps1->draw();//OK Rectangle::draw Shape *ps2 = new Ellipse; ps2->draw();//OK Ellipse::draw ps1->Shape::draw();// OK shape::draw ps2->Shape::draw();//OK shape::draw ps1->error();//ok 非纯虚函数继承了接口和默认版本 shape::error ps1->objectID();//ok 强制继承了基类的实现 shape::objectID delete ps1; delete ps2; system("pause"); return 0;}
条款35:考虑virtual函数以外的其他选择
(一)Non-Virtual Interface(NVI)手法实现Template Method 模式
多态环境中,使用private的virtual函数,和non-virtual的public接口,这样,在public接口中调用virutal函数,同样可以达到多态的效果,并且可以控制在调用virtual函数前和调用virtual函数后执行的动作。
class GameCharacter{public: void healthValue()const { ///before: 加锁(mutex)、日志记录(log)等 doHealthValue(); ///after: 解锁(unmutex)等 }private: virtual void doHealthValue()const { cout << "GameCharacter::doHealthValue" << endl; }};class A :public GameCharacter{private: virtual void doHealthValue()const { cout << "A::doHealthValue" << endl; }};int main(){ GameCharacter *ps = new A; ps->healthValue();//OK A::doHealthValue delete ps; system("pause"); return 0;}
(二)Stratege模式
书中给出了三种实现stratege模式的方式。
关于什么是stratege模式,参考:
http://blog.csdn.net/lihao21/article/details/48008953
Strategy模式是一种行为型设计模式,它将算法一个个封装起来,在某一时刻能够互换地使用其中的一个算法。从概念上看,所有这些算法完成的都是相同的工作,只是实现不同而已。
2.1古典的Strategy模式
借由虚函数实现,比如书中的例子,在游戏设计中,好的NPC和坏的NPC有不同的计算血量的方式。
我们将计算血量这个行为封装成class,并将calc
设定为虚函数,这样,就可以派生出不同的计算血量的class他们对应不同的行为。
下面代码实现书中UML图所示
#include <iostream>#include <string>using namespace std;class HealthCalc{public: virtual void calc() const { cout << "HealthCalc::calc" << endl; }};class Fast :public HealthCalc{public: virtual void calc() const { cout << "Fast::calc" << endl; }};class Slow :public HealthCalc{public: virtual void calc() const { cout << "Slow::calc" << endl; }};HealthCalc defaultCalc;class GameCharacter{public: explicit GameCharacter(HealthCalc *p=&defaultCalc) :pHealthCalc(p){} void healthValue() { pHealthCalc->calc(); } void setMethod(HealthCalc *p) { pHealthCalc = p; }private: HealthCalc * pHealthCalc;};class EvilBadGuy : public GameCharacter{public: explicit EvilBadGuy(HealthCalc *p) :GameCharacter(p){}};class EyeCandyCharacter : public GameCharacter{public: explicit EyeCandyCharacter(HealthCalc *p) :GameCharacter(p){}};int main(){ HealthCalc * p1 = new Fast; HealthCalc*p2 = new Slow; GameCharacter gc; gc.healthValue();//ok HealthCalc::calc gc.setMethod(p1); gc.healthValue();//ok Fast::calc gc.setMethod(p2); gc.healthValue();//ok Slow::calc//.............................// EvilBadGuy badGuy(p1); badGuy.healthValue();//ok Fast::clac EyeCandyCharacter eye(p2);//ok Slow::clac eye.healthValue(); delete p1; delete p2; system("pause"); return 0;}
2.2使用Function Pointer实现Strategy模式
函数指针可以绑定不同的策略函数。
借由上面同样的例子,可以做如下修改,将类改成函数,并经由函数指针来调用:
void defulatCalc(){ cout << "defalutCalc()" << endl;}void fastCalc(){ cout << "fastCalc()" << endl;}void slowCalc(){ cout << "slowCalc()" << endl;}class GameCharacter{public: typedef void(*pHeathFunc)(void); explicit GameCharacter(pHeathFunc p = defulatCalc) :healthFunc(p){} void healthValue() { healthFunc(); }private: pHeathFunc healthFunc;};class EvilBadGuy : public GameCharacter{public: explicit EvilBadGuy(pHeathFunc p) :GameCharacter(p){}};class EyeCandyCharacter : public GameCharacter{public: explicit EyeCandyCharacter(pHeathFunc p) :GameCharacter(p){}};int main(){ EvilBadGuy badGuy(fastCalc); EyeCandyCharacter eyeCandy(slowCalc); badGuy.healthValue(); // fastCalc() eyeCandy.healthValue();// slowClac() system("pause"); return 0;}
2.3使用tr1::function实现Strategy模式
function类似于指针,但是不强调绝对的返回值和参数类型,他是一个兼容模式,这里的兼容模式我理解为调用形式一致,借由bind可以进行参数绑定:
//这里pFunc不仅仅是参数为int返回void的函数指针,他是一个强大的兼容模式。typedef std::tr1::function<void()> pFunc;void fun1(int i){ cout << i << endl;}void func2(int i, int j, int k){ cout << i + j + k << endl;}int main(){ pFunc p1 = std::tr1::bind(fun1, 20); p1();// 1 pFunc p3 = std::tr1::bind(func2, 1,2,3); p3();//6 pFunc p4 = std::tr1::bind(func2, 2, 20, 20); p4();//42 system("pause");// return 0;}
在刚刚的例子中,用function替代函数指针
void defulatCalc(){ cout << "defalutCalc()" << endl;}void fastCalc(){ cout << "fastCalc()" << endl;}void slowCalc(){ cout << "slowCalc()" << endl;}class GameCharacter{public: typedef tr1::function<void(void)>HealthCalcFunc; explicit GameCharacter(HealthCalcFunc p = defulatCalc) :healthFunc(p){} void healthValue() { healthFunc(); }private: HealthCalcFunc healthFunc;};class EvilBadGuy : public GameCharacter{public: explicit EvilBadGuy(HealthCalcFunc p) :GameCharacter(p){}};class EyeCandyCharacter : public GameCharacter{public: explicit EyeCandyCharacter(HealthCalcFunc p) :GameCharacter(p){}};class Foo{public: void FooCalc(int i) { cout << "Foo::FooCalc" << endl; cout << i << endl; }};int main(){ EvilBadGuy badGuy(fastCalc); EyeCandyCharacter eyeCandy(slowCalc); badGuy.healthValue(); // fastCalc() eyeCandy.healthValue();// slowClac() Foo foo; EvilBadGuy anotherBadGuy(tr1::bind(&Foo::FooCalc, &foo,42)); anotherBadGuy.healthValue();// Foo::FooCalc 42 system("pause"); return 0;}
条款36:绝不重新定义继承而来的non-virual函数
重写父类的继承而来的虚函数,目的在于实现多态,使用父类指针调用该函数时在运行时动态绑定特定版本。
本条款强调非虚函数在子类中不要重写:
1.子类中的同名函数会遮盖父类版本。
2.使用父类指针调用该函数达不到多态效果。
非虚函数都是静态绑定的,也即编译期决定了,不具备运行时动态绑定效果。
class Base{public: void mf() { cout << "base::mf" << endl; }};class Derived :public Base{public: void mf() { cout << "Derived::mf" << endl; }};int main(){ Derived d; d.mf();//Derived::mf d.Base::mf();//Base::mf Base * pb = &d; pb->mf();//Base::mf Derived *pd = &d; pd->mf();//Derived::mf system("pause"); return 0;}
条款37:绝不重新定义继承而来的缺省参数值
本条款的前提是,继承一个virtual函数,但这个virtual函数带有缺省参数值。这就非常有意思了,virtual函数是运行时绑定的,而缺省参数值是在编译期决定的静态绑定。
class Shape{public: enum ShapeColor{ Red, Green, Blue}; virtual void draw(ShapeColor color = Blue) const = 0;};class Rectangle :public Shape{public: //赋予不同的缺省参数、真糟糕! virtual void draw(ShapeColor color = Green) const { cout << "Rectangle::draw" << endl; cout << color << endl; }};class Circle :public Shape{ //调用时需要指定参数 //静态绑定下不从base继承缺省参数值 //但若以指针或引用来调用则可以不指定 //因为动态绑定这个函数会继承base的缺省值。public: virtual void draw(ShapeColor color) const { cout << "Circle::draw" << endl; cout << color << endl; }};int main(){ Circle c; //c.draw();//error 调用的参数太少 c.draw(Shape::Green);// OK Circle::draw 1 Shape *pc = &c; //ok base的缺省参数被继承下来 pc->draw(); // circle::draw 2 Rectangle r; Shape*pr = &r; pr->draw();//rectangle::draw 2 system("pause"); return 0;}
条款38:通过符合塑模出has-a或“根据某物实现出”
has-a
和is-implemented-in-terms-of
这两种情况分别举例:
has-a复合关系:
class Address{};class Name{};class Person{ private: Address a; Name n;};
is-implemented-in-terms-of复合关系
例如想通过STL标准容器list
作为底层container实现一个Set
template <class T>class Set{ public: void insert(const T& t); private: std::list<T> rep;};template <class T>void Set<T>::insert(const T& t){ if(!member(t))//set中元素互斥 rep.push_back(t);}
条款39:明智而审慎地使用private继承
先通过一个例子来看看
class Person{};class Student :private Person{};void eat(const Person &p){ cout << "eat" << endl;}int main(){ Person p; Student s; eat(p); //eat(s);//error 不允许对不可访问的基类进行转换 //Person *pp = &s;//error 不允许对不可访问的基类进行转换 system("pause"); return 0;}
private继承的两条规则:
1.编译器不会自动将一个derived class对象转换成一个base class对象
2.在基类中的继承而来的所有成员属性都会变成Private
private继承意味着is-implemented-in-terms-of
表示经由某某实现,跟上述条款38“复合”类似。
私有继承纯粹是一种实现技术,基类的实现被继承下来,接口却没有。
怎么理解,私有继承后,基类中的成员在派生类中变成了private,这样派生类的对象无法通过基类的接口进行调用,然而派生类却可以在自己的公有接口实现中调用基类的protected和public的函数。
class Base{public: void func1(){ cout << "func1" << endl; } void func2(){ cout << "func2" << endl; }protected: void func3(){ cout << "func3" << endl; }};class Derived :private Base{public: void func4() { //借由基类中方法实现的部分 func1(); func2(); func3(); //自己的实现 cout << "func4" << endl; }};int main(){ Derived d;// d.func1();//error 不可访问 d.func4();//ok func1 func2 func3 func4 system("pause"); return 0;}
这个例子中Derived class中的func4借由继承而来的func1~func3
的实现完成,而Derived的对象无法通过func1接口调用(私有继承后不可访问Private)。
可以看到:private继承意味着只有实现部分被继承,接口部分应省略,称之为implemented-in-terms-of
另外这里还提到了一个作用,叫做EBO(empty base optimization)
我们知道sizeof一个空类得到的大小是1,这是编译器将默默安插一个char,表示这个类的存在。如下代码由于字节对齐,可能会耗费更多的内存:
class empty{};class hold{ private: int x; empty e;};sizeof(hold);//8
空基类优化指的是让派生类继承这个基类:
class hold :private empty{ private: int x;}sizeof(hold);//4
至于为什么要使用EBO空基类优化,参照:
C++ EBO空基类优化
条款40:明智而审慎地使用多重继承
多重继承可能带来歧义,看下面的例子:
class BorrowableItem{public: void checkOut(){}};class ElectronicGadget{private: bool checkOut(){}};class MP3 : public BorrowableItem, public ElectonicGadget{ };int main(){ MP3 mp; mp.checkOut();//error checkOut不明确 mp.BorrowableItem::checkOut();//ok getchar(); return 0;}
虽然ElectronicGadget
中的checkOut并不能访问,不过C++是首先确认这个函数对此调用之言是最佳匹配。因此private checkOut就未能被编译器通过。
在多重继承中,如果存在base class不出现在最高级base class,则出现了“钻石型多重继承”,在我以前的学习过程中,这被称为菱形继承。例如:
class File{public: int file; };class Input : public File{};class Output : public File{};class IO :public Input, public Output{};int main(){ IO io; io.file = 0;//file 不明确 getchar(); return 0;}
这是有问题的,因为成员变量可以经由两条路径被复制,这样最终到了最底层的派生类之后就变得不明确。
解决的办法是使用virtual继承:
class File{public: int file; };class Input : virtual public File{};class Output : virtual public File{};class IO :public Input, public Output{};int main(){ IO io; io.file = 42;//OK cout << io.file << endl; getchar(); return 0;}
但是,virtual继承需要付出一定的代价:
1.virtual 继承的那些classes所产生的对象往往比使用non-virtual的大
2.访问virtaul base class的成员变量时也比non-virtual base class慢。
书中最后给出了一个使用多重继承的好处
比如说,伪代码如下:
IPerson//接口IPerson & makePerson(DataBaseID id);DataBaseID id;IPerson & pp = makePerson(id)
可以想见,通过makePerson
这个工厂函数来创建Person对象,这些Person类都继承自IPerson。
假设现在要写一个CPerson类,我们可以不必从头写起,我们可以经由一个工具类PersonInfo来实现is-implemtented-in-terms-of
,但是PersonInfo不一定完全符合我们的条件,这个时候,需要在CPerson类中进行重写虚方法。
这就是多重继承的例子,他的继承体系是一个多重继承。
IPerson//接口PersonInfo//工具类包含一些需要虚函数方法CPerson :public IPerson,private PersonInfo//在CPerson根据需求重写PersonInfo中的某些虚函数方法。
- Effective c++(笔记)之继承关系与面向对象设计
- [Effective C++] 继承与面向对象设计
- 《Effective C++》继承与面向对象设计
- Effective C++笔记(8)—继承与面向对象设计
- Effective C++(六)继承与面向对象设计
- Effective C++(六)继承与面向对象设计
- effective C++: 6.继承与面向对象设计
- Effective C++(六)继承与面向对象设计
- <<Effective C++>> 读书笔记6: 继承与面向对象设计
- 《Effective C++》第六章:继承与面向对象设计
- effective C++ 继承与面向对象设计 笔记
- effective C++ 学习笔记 实现&&继承与面向对象设计
- Effective C++ 第六章--继承与面向对象设计笔记
- Effective C++ -- 继承与面向对象设计
- Effective C++ ——继承与面向对象设计
- Effective C++ — 继承与面向对象设计
- Effective C++ 读书笔记(六) 继承与面向对象设计
- Effective C++读书笔记---继承与面向对象设计
- A+B
- mac paralles内 windows虚机 连接 linux虚机
- HDU2544 最短路
- [leetcode]636. Exclusive Time of Functions
- CNTK API文档翻译(11)——使用LSTM预测时间序列数据(物联网数据)
- Effective C++笔记(8)—继承与面向对象设计
- Nginx服务器对session的处理策略
- 不要以多态的方式处理数组
- python 调用super()初始化报错“TypeError: super() takes at least 1 argument”
- va_list va_start va_arg va_end 使用方法
- 怎么上传自己的代码到github上
- java泛型
- [spring-boot] 集成shiro
- 刘汝佳--最长回文字符串