Effective C++笔记—继承与面向对象设计
Posted NearXDU
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Effective C++笔记—继承与面向对象设计相关的知识,希望对你有一定的参考价值。
条款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() = 0cout << "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 Filepublic: 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 Filepublic: 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++笔记—继承与面向对象设计的主要内容,如果未能解决你的问题,请参考以下文章
Item 41:隐式接口与编译期多态 Effective C++笔记
Item 39:明智地使用private继承 Effective C++笔记