C++ 读书笔记 Effective C++(三)

public继承意味着is-a;virtual函数意味着接口必须被继承 none-virtual函数意味着接口和实现都必须被继承。

32.确定你的public继承塑模出is-a关系

       “public继承”意味is-a.适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base class对象
       两个问题:1.“所有鸟多会飞,企鹅是鸟,但是企鹅不会飞”.  2.”class Square 应该以public形式继承class Retangle吗?”
        对于第一个问题:“你应该宁可采取”在编译期拒绝企鹅飞行“的设计,而不是”只在运行期才能侦测它们”的设计。
        对于第二个问题:”本例的根本困难是,某些可施行于矩形身上的事情却不可以施行于正方形身上(宽度总是应该和高度一样)。但是       public 继承主张,能够施行于base class对象身上的每件事情,也可以施行于derived class对象身上。在正方形和矩形例子中,那样的主张无法保持,所以以public继承塑模它们之间的关系并不正确."

     “public继承”意味着is-a。适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived classes对象也都是一个base classes对象。

33.避免遮掩继承而来的名称
  class Base
{
public:
   
 virtual void mf1() = 0;
   
 virtual void mf1(int);
   
 virtual void mf2();
   
 void mf3();
   
 void mf3(double);
private:
   
 int x;
};


class Derived :public Base
{
public:
    virtual void mf1();
   
 void mf3();
   
 void mf4();
};

void Test()
{
   
 Derived d;
   
 int x = 1;
   
    d.
mf1();
   
 //d.mf1(2); //错误!因为Derived::mf1遮掩了Base::mf1
    d.
mf2();    //调用的是Base::mf2
    d.
mf3();    //调用的是Base::mf3
   
 //d.mf3(x);   //错误!因为Derived::mf1遮掩了Base::mf3
    d.
mf4();    //调用的是Derived::mf4
   
}
   即使 base classes和derived classes内的函数有不同的参数类型也适用,而且不论函数是virtual和non-virtual一体适用。这些行为背后的基本理由是为了防止你在程序或应用框架内建立新的derived calss时附带地从疏远地base classes继承重载函数。不幸地是你通常会想继承重载函数。实际上如果你正在使用public继承而又不继承那些重载函数,就是违反base和derived classes之间地is-a关系。

class  Base
{
public:
   
 virtual void mf1() = 0;
   
 virtual void mf1(int);
   
 virtual void mf2();
   
 void mf3();
   
 void mf3(double);
private:
   
 int x;
};


class Derived :public Base
{
public:
   
 using Base::mf1;
   
 using Base::mf3;
   
   
 virtual void mf1();
   
 void mf3();
   
 void mf4();
};

void Test()
{
   
 Derived d;
   
 int x = 1;
   
    d.
mf1();    //调用的是Derived::mf1
    d.
mf1(2);   //调用的是Base::mf1
    d.
mf2();    //调用的是Base::mf2
    d.
mf3();    //调用的是Base::mf3
    d.
mf3(x);   //调用Base::mf3
    d.
mf4();    //调用的是Derived::mf4
   
}
注意:1.base class内的public名称在publicly derived class内也应该是public.
     2.使用using声明会令继承而来的某给定名称之所有同名函数在derived classes中都可见。可以使用一个简单的转交函数(forward function),例如我们想在Derived内只使用mf1的午参数版本可以这样来做:

   
 virtual void mf1()
    {
       
 Base::mf1();
    }

总结:
 derived classes内的名称会遮掩base classes内的名称。在public 继承下从来没有人希望如此。
 为了让被遮掩的名称再现天日,可使用using声明式或转交函数(forwarding function)。

34.区分接口继承和实现继承

纯虚函数有两个最突出的特性:它们必须被任何“继承了它们”的具象class重新声明,而且它们在抽象class中通常没有定义。即:声明一个纯虚函数的目的是让derived classes只继承函数接口。
注:我们可以为纯虚数提供定义,但调用它的唯一途径是”调用时明确指出其class名称”.不过一般而言这项性质用途有限。

class IShape
{
private:
   
 int objID;
public:
   
 virtual void draw() const = 0;
   
 virtual void error(const std::string& msg);
   
 int objectID() const {return objID;}
};
void IShape::draw() const
{
}
class Retangle:public IShape
{
public:
   
 virtual void draw() const;
   
 virtual void error(const std::string& msg);
};
void Retangle::draw() const
{
   
 //defalut 
}
void Retangle::error(const std::string& msg)
{
}

class Ellipse:public IShape
{
public:
    Ellipse();
   
 virtual void draw() const {}
   
 virtual void error(const std::string& msg){}
   
};


void Test()
{
    IShape* ps1 = new Retangle;
    ps1->
draw();
   
 IShape *ps2 = new Ellipse;
    ps2->
draw();
    ps1->
IShape::draw();    //调用Shape::draw
    ps2->
IShape::draw();    //调用Shape::draw
}

        与纯虚函数有点不同,对于虚函数派生类继承其函数接口,但虚函数会提供一份代码实现,派生类可覆盖它。声明虚函数的目的,是让派生类继承该函数的接口和缺省实现。
        如上面的error接口,每个class都必须支持一个“当遇上错误时可调用”的函数,但每个class可自由处理错误。如果某个class不行针对错误做出任何特殊的行为,它可以退回到Shape class提供的缺省错误处理。也就是说Shape::error的声明式告诉派生类的设计者,“你必须支持一个error函数,但如果你不想自己写一个,可以使用Shape class提供的缺省版本。
         声明non-virtual函数的目的是为了令derived classes继承函数的接口以及一份强制实现。如果成员函数有一个non-virtual函数,意味者它并不打算在derived classes中有不同的行为。实际上一个non-virtual成员函数所变现的不变性凌驾其特异性。

总结:
接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。 
pure virtual函数只具体指定接口继承。
简朴(非纯)impure vritual 函数具体指定接口继承及缺省实现继承。
non-virtual函数具体指定接口继承以及强制实现继承。

35.考虑virtual函数以外的其它选择

令客户端通过public non-virtual成员函数间接调用private virtual函数,称为non-virtual interface手法,它也式所谓的Template Method设计模式的一个独特表现形式。把这个non-virtual函数称为virtual函数的外覆盖器。

#include <iostream>
using namespace std;

class GameCharacter
{
public:
   
 int healthValue() const            //derived  classes 不重新定义它
    {
       
 //.......                       //做一些事前工作,详下
       
 int reVal = doHealthValue();    //做真正的工作
       
 //......                        //做一些事后工作,详下
       
       
 return reVal;
    }
private:
   
 virtual int doHealthValue() const  //derived classes可重新定义它
    {
       
 //....                          //缺省计算,计算健康指数
    }
};

class EvilBadGuy:public GameCharacter
{
public:
   
 virtual int doHealthValue() const
    {
       
 cout << "EvilBadGuy health value." << endl;
    }
private:
};


void TestGameCharacter()
{
   
 EvilBadGuy obj;
    obj.
healthValue();
}
#endif



古典的Strategy模式
class  GameCharacter;
class HealthCalcFunc
{
public:
   
 virtual int calc(const GameCharacter& gc) const{};
};

HealthCalcFunc defaultHealthCalc;
class GameCharacter
{
public:
   
 explicit GameCharacter(HealthCalcFunc* phcf=&defaultHealthCalc):pHealthCalc(phcf)
    {
    }
   
 int healthValue() const
    {
       
 return pHealthCalc->calc(*this);
    }
private:
   
 HealthCalcFunc* pHealthCalc;
};
class EvilBadGuy : public HealthCalcFunc
{
public:
   
 virtual int calc(const GameCharacter& gc) const
    {
       
 cout << "EvilBadGuy health value." << endl;
    }
};

void TestGameCharacter()
{
   
 EvilBadGuy obj;
   
 GameCharacter gc(&obj);
    gc.
healthValue();
}

总结:
virtual函数的替代方案包括NVI(non-virtual interface)手法以及strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。
将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员
tr1::function对象的行为就像一般函数指针。这样的对象可接纳”与给定之目标签名式兼容”的所有可调用物。


36.绝不重新定义继承而来的non-virtual函数
  

 #include <iostream>
using namespace std;

class B
{
public:
   
 void mf(){cout << "B class" << endl;}
private:
};

class D:public B
{
public:
   
 void mf(){cout <<"D class" << endl;}
};

void TestInterface()
{
   
 D x;
   
 B* pb = &x;
   
 D *pd = &x;
   
    pb->
mf();  // "B class"
    pd->
mf();  // "D class"
   
}
non-virtual函数B::mf和D::mf都是静态绑定的,这意思是说,由于pb被声明为一个pointer-to-B,通过PB的non-virtual函数永远是B所定义的版本,即使PB指向的一个类型是”B派生之class”的对象。
如果你编写class D并重新定义继承自class B的non-virtual函数mf,D对象很可能展现出精神分裂的不一致行径。
在class内声明一个non-virtual函数会为改class建立一个不变性,凌驾其特异性。

总结:任何情况下都不该重新定义一个继承而来的non-virtual函数。


37.绝不重新定义继承而来的缺省参数值

#include <iostream>
using namespace std;
class B
{
public:
   
 virtual void mf(int count=10){cout << "B::count: " << count << endl;}
private:
};

class D:public B
{
public:
   
 virtual void mf(int count = 20){cout <<"D::count: " << count << endl;}
};

void TestInterface()
{
   
 D x;
   
 B* pb = &x;
   
 D* pd = &x;
   
    pb->
mf();  // “D::count: 10"
    pd->
mf();  // "D::count: 20"
   
}

在上面的例子中,pb和pd都被声明为指针类型,它们都是静态类型。注意,不论它们真正指向什么,它们的都是静态类型,pb的静态类型为 B* ,PB的静态类型为D*.
对象的所谓动态型则是指“目前所指对象的类型”,也就是说,动态类型可以表现一个对象将会有什么行为。pb的动态类型是D*,pd的动态类型是D*。
virtual函数动态绑定,意思是调用一个virtual函数时,究竟调用哪一份函数实现代码,取决于发出调用的安哥对象的类型。注意:virtual函数是动态绑定的,但是缺省参数是静态绑定的。
pb视图调用一个定义于derived class内的virtual函数的同时,却使用base class为它所指定的缺省值。


为了防止这样的情况发生可以使用NVI技术。
class B
{
public:
  
 void mfInterface(int count=10){ mf(count); }
private:
   virtual void mf(int count){cout << "B::count: " << count << endl;}
};

class D:public B
{
public:
    
virtual void mf(int count){cout <<"D::count: " << count << endl;}
};

void TestInterface()
{
   
 D x;
   
 B* pb = &x;
   
 D* pd = &x;
   
    pb->
mfInterface();  // "D::count: 10"
    pd->
mfInterface();  // "D::count: 10"
   
}

由于non-virtual函数应该绝对不被derived classes覆写,整个设计使得mf函数的count缺省值总是为10

总结: 绝对不要重新定义一个继承而来的缺省值,因为缺省值都是静态绑定,而virtual函数-你唯一应该覆写的东西是动态绑定。

38.通过复合塑模出has-a或”根据某物实现出”
“public继承”带有is-a的意义。复合也有它自己的意义。实际上他有两个意义。复合意味has-a或is-implemented-in-terms-of(根据某物实现).

总结:复合的意义和public继承完全不同
     在应用域,复合意味着has-a有一个。在实现域,复合意味着is-implemented-in-terms-of(根据某物实现出).


39.明智而审慎的使用private继承
如果classes之间继承关系是private,编译器不会自动将一个derived class对象(例如Student)转换为一个base class对象。 由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们在base class中原本是protected或public属性。Private继承意味implemented-in-terms-of(根据某物实现出).

总结:Private继承意味is-implemented-in-terms-of(根据某物实现出)。它通常比复合的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
              和复合不同,private继承可以造成empty base最优化。这对致力于"对象尺寸最小化"的程序开发者而言,可能很重要。

40.明智而审慎地使用多重继承
使用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们体积大,访问virtual base classes的成员变量时,也比访问non-virtual base class的成员变量速度慢。
对使用virtual base class的忠告很简单。第一,非必要不使用virtual classes。平时使用non-virtual继承。第二,如果你必须使用virtual base class尽量避免在其中放置数据。这样一来就不需要担心这些classes身上的初始化(和赋值)所带来的诡异事情了。

总结:多重继承比单一继承复杂。他可能导致新的歧义性,以及对virtual继承的需要。
virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具使用价值的情况。
多重继承的确有正当的用途。其中一个情节涉及"public继承某个Interface class"和”private继承某个协助实现的class“的两相同组合。

你可能感兴趣的:(C++,读书笔记,effective-c++)