条款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)
{
//不继承默认行为,自己实现
}
};
非虚函数,意味着它并不打算在继承类中有不同的行为。即一个废墟成员函数所表现的不变性大于它的特异性,表示物理继承类变得多么不一样,它的行为应该一样。即是为了令继承类继承函数的接口以及一份强制性实现。所以它不应该在继承类中被定义。