条款35:确定你的public inheritance,模塑出“isa”的关系
一、企鹅和鸟
class Bird{
public:
virtual void fly();//鸟可以飞
...
};
class Penguin:public Bird{ //企鹅是一种鸟
...
};
以上继承体系说企鹅可以飞;其实当我们说鸟可以飞的时候并不是指所有鸟都可以飞。
二、长方形和矩形
class Rectangle{
public:
virtual void setHeight(int newHeight);
virtual void setWidth(int newWidth);
virtual int height() const;
virtual int width() const;
....
};
void makeBigger(Rectangle& r)//增加r的面积
{
int oldHeight = r.height();
r.setWidth(r.width()+10);
assert(r.height() == oldHeight); //判断r的高度是否未曾改变
}
class Square:public Rectangle { .... };
Square s;
...
assert(s.Width() == s.height());
makeBigger(s);
assert(s.width() == s.height());
三个assert出现无法调解的情况。
条款36:区分接口继承(interface inheritance)和实现继承(implementation inheritance)
一、Member function的接口总是会被继承,所以任何事情只要对base class而言为真,就一定对其derived classes为真。
二、声明一个纯虚拟函数的目的是为了让derived classes只继承其接口
三、声明一般(非纯)虚拟函数的目的,是为了让derived classes继承该函数的接口和缺省行为。
有时候你可能忘了重写虚拟方法,导致子类继承了基类的方法而出现bug。这时你可以用这种机制:
class Airplane{
public:
virtual void fly(const Airport& destination) = 0;
....
protected:
void defaultFly(const Airport& destination)
{ default code for flying ; }
};
class Model:public Airplane{
public:
virtual void fly(const Airport& destination)
{ defaultFly(destination); }
};
这样子类就必须要求给出fly的重写,而且仍然可以显示调用默认的fly函数。
同时,利用“纯虚拟函数必须在subclasses中重新声明,但是纯虚拟函数也可以拥有自己的定义”这条规则来更好的实现上面的机制
class Base
{
public:
virtual void print() = 0;
};
void Base::print()
{
cout<<"Base print"<<endl;
}
class Derived:public Base
{
public:
virtual void print()
{
Base::print();
}
};
四、声明非虚拟函数的目的是为了令derived classes继承函数的接口和实现
可能会遇到的两个错误:
1、第一个错误是将所有函数声明为非虚拟函数,这使得derived classes没有富裕空间进行特殊化工作。
2、另一个错误是将所有member function都声明为虚拟函数,不但降低了效率还使得函数太过泛泛。
条款37:绝对不要重新定义继承而来的非虚拟函数
class Base
{
public:
void mf()
{ cout<<"Base::mf()"<<endl;}
};
class Derived:public Base
{
public:
void mf()
{ cout<<"Derived::mf()"<<endl;}
};
void main()
{
Derived x;
Base *p = &x;
p->mf();
Derived *pp = &x;
pp->mf();
}
两次调用mf()都不同 因为两个mf()都是静态绑定的,不同过虚拟表,哪个类的指针就调用哪个类的函数。另一方面,如果是动态绑定(虚函数),不管是p还是pp都会调用Derived的mf(),因为p和pp真正指向的都是一个D对象。
结论:
1、因为D是一个B,所以B的非虚拟方法都继承下来,如果D的mf真的很B的mf不同,那么“每个D都是一种B”就不为真了,D不应该是B的子类;
2、如果D确实是B的子类,为了反映出多态性,mf应该声明为虚拟函数;
两种解释都支持条款37的观点
条款38:绝对不要重新定义继承而来的缺省参数值
局限于继承一个带有缺省参数值的虚拟函数
Shape *ps;
Shape *pc = new Circle;
Shape *pr = new Rectangle;
ps pc pr的静态型别都是Shape* 但是动态型别就不一样了,pc是Circle*而pr是Rectangle*,ps并没有动态型别,因为它尚未指向任何对象。
假如 ps = pc;那是ps的动态型别也变成Circle。虚拟函数系动态绑定而来,调用哪个函数取决于对象的动态型别。
但是当 pr->Draw();时,会调用Rectangle的Draw(),但是缺省参数值来自于Shape Class的RED,为什么呢?因为缺省参数是静态绑定的,他依据的是静态型别。C++这样做,是为了效率。
条款39:避免在继承体系中做向下转型(cast down)动作
先来看传统方式,
for(list<BankAccount*>::interator p = allAccounts.begin();
p != allAccounts.end();
++p ) {
SavingsAccount *psa;
CheckingAccount *pca;
if (psa = dynamic_cast<SavingsAccount*>(*p)) {
psa->creditInterest();
}
else if (pca = dynamic_cast<CheckingAccount*>(*p)) {
pca->creditInterest();
}
else {
error("Unknown account type !");
}
}
必然导致if-then-else程序风格,如果使用static_cast强制转换效果更差,当用dynamic_cast运用到指针上时,如果成功,那么指针的动态型别与其转型目标一致;如果失败,会传回null指针。
而解决该机制的最佳办法就是将转型动作以虚拟函数的调用取代。
条款40:通过layering技术来模塑has-a或is-implemented-in-terms-of的关系
一、模塑has-a技术
class Address { ... };
class PhoneNumber { ... };
class Person {
public:
...
private:
string name;
Address address;
PhoneNumber voiceNumber;
PhoneNumber faxNumber;
};
二、模塑is-implemented-in-terms-of技术(根据某物实现)
template<class T>
class Set {
public:
bool member(const T& item) const;
void insert(const T& item);
void remove(const T& item);
int cardinality() const;
private:
list<T> rep;
};
这个set类底层实际是通过list实现的。
条款41:区分inheritance和templates
一、template应该用来产生一群classes,其中对象型别不会影响class的函数行为
如Stack类
template<class T> class Stack {
};
无论T是什么型别,都不影响栈的函数行为。
二、inheritance应该用于一群classes上,其中对象型别会影响class的函数行为