Effective C++第6章继承与面向对象设计(条款32-34)

条款32:确定你的public继承塑膜出is-a关系。

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


条款33:避免遮掩继承而来的名称

derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。

为了让被遮掩的名称再见天日,可使用using声明式或转交函数。

    //如以下类  
    class Base  
    {  
    private:  
        int x;  
    public:  
        virtual void mf1()=0;  
        virtual void mf1(int);  
      
        virtual void mf2();  
          
        void mf3();  
        void mf3(double);  
    };  
      
    class Derived : public Base  
    {  
    public:  
        virtual void mf1();   //这里会将基类的mf1,mf1(int)都隐藏掉,遮盖了名称  
        void mf3(); //这里也如mf1一样,将基类的两个mf3遮盖  
        void mf4();  
    };  
      
    Derived d;  
    int x;  
      
    d.mf1();    //Derived::mf1  
    d.mf1(x);   //错误,因为继承类掩盖了基类的mf1(int)  
    d.mf2();    //Derived::mf2  
    d.mf3();    //Derived::mf3  
    d,mf3(x);   //错误,掩盖了基类的mf3(int)  
 

      以上,base中的mf1和mf3函数都被继承类的mf1, mf3遮盖掉了!即使参数是不同的也遮盖掉了,而且不论函数是否是virtual或non-virtual都一样。这和局部对象会遮盖全局对象,即使这两个东东是不同的类型也是!为了避免上面的名称掩盖,应该使用using声明式,这会使基类内的名称都可见。如下:

//为了避免名称被掩盖,1 public继承下可以使用using声明式
class Base
{
private:
	int x;
public:
	virtual void mf1()=0;
	virtual void mf1(int);

	virtual void mf2();
	
	void mf3();
	void mf3(double);
};

class Derived : public Base
{
public:
	using Base::mf1;  //让base类名为mf1和mf3的所有东西(函数了变量了)
	using Base::mf3;  //在Derived作用域内都可见(并且是public)
	virtual void mf1(); 
	void mf3();
	void mf4();
};
d.mf1();    //Derived::mf1
d.mf1(x);   //没问题,Base::mf1(int)
d.mf2();    //Derived::mf2
d.mf3();    //Derived::mf3
d.mf3(x);   //没问题,Base::mf3(int)
        以上意味着如果继承基类并继承所有重载函数,而又希望重新定义或覆写其中一部分,则必须为原本那些会被名称遮盖的每个名称引入一个uisng声明,否则某些你希望集成的名称会被掩盖。using在继承类的public中写因为基类这些函数也是public.

        但如果Derived以私有形式继承Base,而继承类唯一向集成的是mf1那个无参数版本,using则不行,因为Using会使令继承而来的某给定名称的所有同名函数在继承类中课件。这种情况下,只需要继承一个,可以使用转交函数,如下:

   

class Derived : private Base
{
public:
	virtual void mf1()
	{
		Base::mf1();   //转交函数,暗自成为inline
	}
};
d.mf1();    //Derived::mf1
d.mf1(x);   //错误,名称被掩盖
         注意以上是private继承,因为public继承意味者is-a,应该保持基类原有的模样,私有继承则不同。利用转交函数可以调用基类mf1的无参版本,并且依然将有参版本掩盖。inline转交函数的另一个用途是为那些不支持using声明式的老旧编译器另辟一条新路,将继承而得的名称在继承类作用域内可见。


条款34:区分接口继承与实现继承

接口继承与实现继承不同。在public继承下,derived class总是继承base class接口。

pure virtual函数只具体指定接口继承;

简朴的(非纯)impure virtual函数具体指定接口继承及缺省实现继承;

non-virtual函数具体指定接口继承以及强制性实现继承;

          纯虚函数只是为了让继承类继承函数接口,但是可以为纯虚函数提供定义,但调用它的唯一途径就是通过“调用时明确指出其class名称”

          声明非纯虚函数是为了让继承类继承该函数的接口和实现继承,考虑以下的例子,

      

class Airport {...};
class Airplane
{
public:
	virtual void fly(const Airport& destination);
	...
};

void Airplane::fly(const Airport& destination)
{
	缺省代码,将飞机飞向指定的目的地
}

class ModelA : public Airplane {...};
class ModelB : public Airplane {...};
      以上继承体系,A,B中fly的相同行为可以写入基类的fly,如果两者飞行方式相同,则不需重新定义fly,可以直接继承基类的实现。如果有区别可以调用基类的,不同的行为可以再在这个函数中写出来。但如果新增一个飞机C,且飞行方式不一样,即不应该调用基类的缺省行为,而如果你有忘了重新定义fly函数,则会出现问题,默认调用基类的飞行方法,而这不是C的飞行方法,是不对的,如果想防止这种情况发生,即继承类如果要想继承基类默认行为,需要显示调用,否则拒绝。即基类实现“提供缺省实现给derived classes”,但除非它们明确提出才能调用缺省实现,有以下两种方法,一种方法是切断“virtual”函数接口和“实现接口”之间的链接,将fly变成纯虚的,然后默认行为放入另外一个函数,这样基类要想调用默认行为,只能显示调用,如下:

class Ariplane
{
public:
    //声明为纯虚的保证子类必须被重写
    virtual void fly(const Airplane& destination=0)=0;
	...
protected: //定义为protected是因为只有继承类需要此函数,外部不应调用,也不关心
	//将默认行为移至另外一个函数,使得子类必须显示调用该函数才能继承默认行为
	void defaultFly(const Airport& destination);	
};

void Airplane::defaultFly(const Airport& destination)
{
	提供缺省行为
}

class ModelA : public Airplane
{
public:
	virtual void fly(const Airport& destination)
	{
		defaultFly(destination);
	}
};


class ModelB : public Airplane
{
public:
	virtual void fly(const Airport& destination)
	{
		defaultFly(destination);
	}
};

//这样新增的moduleC不可能意外继承fly的实现版本了,必须自己提供,且如果不继承
//默认行为则要自己写出来
class ModelC : public Airplane
{
public:
	virtual void fly(const Airport& destination)
	{
		//不继承默认行为,自己实现
	}
};
      但以上方法有人认为不应该以不同的方法分别提供接口和实现继承,可以采用第二种方式,即可以利用纯虚函数必须在derived classes中重新声明,但他们也可拥有自己的实现这个事实,给基类的纯虚fly函数提供一个缺省实现,也可以实现以上类似的功能,如下:

//给基类的纯虚fly函数提供一个缺省实现
class Ariplane
{
public:
    //声明为纯虚的保证子类必须被重写
    virtual void fly(const Airplane& destination=0)=0;
	...
};

void Airplane::fly(const Airport& destination)
{
	提供缺省行为
}

class ModelA : public Airplane
{
public:
	virtual void fly(const Airport& destination)
	{
		Airplane::fly(destination);
	}
};


class ModelB : public Airplane
{
public:
	virtual void fly(const Airport& destination)
	{
		Airplane::fly(destination);
	}
};

//这样新增的moduleC不可能意外继承fly的实现版本了,必须自己提供,且如果不继承
//默认行为则要自己写出来
class ModelC : public Airplane
{
public:
	virtual void fly(const Airport& destination)
	{
		//不继承默认行为,自己实现
	}
};          

         非虚函数,意味着它并不打算在继承类中有不同的行为。即一个废墟成员函数所表现的不变性大于它的特异性,表示物理继承类变得多么不一样,它的行为应该一样。即是为了令继承类继承函数的接口以及一份强制性实现。所以它不应该在继承类中被定义。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值