天天看点

Effective C++学习笔记 第六弹 35-41

条款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:绝对不要重新定义继承而来的缺省参数值

Effective C++学习笔记 第六弹 35-41

局限于继承一个带有缺省参数值的虚拟函数

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)动作

Effective C++学习笔记 第六弹 35-41

先来看传统方式,

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指针。

而解决该机制的最佳办法就是将转型动作以虚拟函数的调用取代。

Effective C++学习笔记 第六弹 35-41

条款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的函数行为