“public继承”意味is-a。适用于base classes身上的每一件事一定也适用于derived classes上,因为每一个derived classes对象也都是一个base class对象。
int x;
void someFunc()
{
double x;
cin>>x; //尽管someFunc的x是double,global x是int,但不要紧。C++名称遮掩规则仍会遮掩名称。即类型是否相同并不重要。
}
class Base{ public: virtual void mf1()=0; virtual void mf1(int) {cout<<"Base::mf1(int)"<<endl;} virtual void mf2() {cout<<"Base::mf2()"<<endl;} void mf3() {cout<<"Base::mf3()"<<endl;} void mf3(double) {cout<<"Base::mf3(double)"<<endl;}; }; class Derived:public Base{ public: //using Base::mf1; //using Base::mf3; virtual void mf1() {cout<<"Derived::mf1()"<<endl;} void mf3() {cout<<"Derived::mf3()"<<endl;} void mf4() {cout<<"Derived::mf4()"<<endl;} }; |
Derived d; int x=0; d.mf1(); d.mf1(x); //error, “Derived::mf1”: 函数不接受1 个参数 d.mf2(); d.mf3(); d.mf3(x); //error, “Derived::mf3”: 函数不接受1 个参数 Derived::mf1遮掩了Base::mf1, Derived::mf3遮掩了Base::mf3。 以作用域为基础的“名称遮掩规则”即使base classes和derived classes内的函数有不同的参数类型也适用,而且不论是virtual或non-virtual。 这些行为背后的原理是防止你在程序库或应用构架内建立新的derived classes时附带地从疏远的base classes继承重载函数。
将注释的代码去掉,即可通过编译,即让Base class内名为mf1和mf3的所有东西在Dervied 作用域内可见。
|
继承方式 基类属性 |
public |
protected |
private |
公有继承(public) |
public |
protected |
不可见 |
保护继承(protected) |
protected |
protected |
不可见 |
私有继承(private) |
private |
private |
不可见 |
对于私有继承,继承后,派生类在自己的内部依旧可以访问其基类的public和protected方法,但在外部访问该派生类时不能访问其基类的public方法了,因为已经变成私有的了。
class Shape{
pubilc:
virtual void draw( ) const = 0; //pure virtual函数
virtual void error( const std::string & msg ); //impure virtual (不纯的 虚函数)
int objectID ( ) const; //non-virtual 非虚函数
};
class Rectangle: public Shape{ … }
class Ellipse: public Shape { … }
n pure virtual函数有两个突出的特性:它们必须被任何“继承了它们”的具象class重新声明,而且它们在抽象class中通常没有定义。声明一个pure virtual函数的目的是为了让derived class只继承函数接口。(可以为pure virtual函数提供一份实现代码,但调用时必须明确指出其class名称)。
Shape* ps = new Shape; //Error, shape是抽象的
Shape* ps1 = new Rectangle; ps1->draw();
Shape* ps2 = new Ellipse; ps2->draw();
ps1->Shape::draw(), ps2->Shape::draw();
n impure virtual函数必须提供一份实现代码,derived class可能覆写(override)它。声明impure virtual函数的目的是为让derived class继承函数的接口和缺省实现。
n non-virtual函数表示它不打算在derived class中有不同的形为。
class GameCharacter
{
public:
int healthValue( ) const
{ …
int retVal = doHealthValue ( );
…
return retVal;
}
private:
virtual int doHealthValue( ) const
{ …
}
}
n 藉由Function Pointers实现Strategy模式
class GameCharacter;
int defaultHealthCalc ( const GameCharacter& gc);
class GameCharacter{
public:
typedef int (*HealthCalcFunc) (const GameCharacter&);
explicit GameCharacter (HealthCalFunc hcf = defaultHealthCalc) : healthFunc (hcf){}
int healthValue( ) const {return healthFunc (*this);}
private:
HealthCalcFunc healthFunc;
};
class EvilBadGuy : public GameCharacter{
public:
explicit EvilBadGuy ( HealthCalcFunc hcf = defaultHealthCalc ): GameCharacter (hcf) {…}
};
int loseHealthQuickly ( const GameCharacter&);
int loseHealthSlowly (const GameCharacter& );
EvilBadGuy ebg1(loseHealthQuickly ), ebg2(loseHealthSlowly );
n 古典的Strategy模式
class GameCharacter;
class HealthCalcFunc{
public: …
virtual int calc (const GameCharacter& gc)const { .. }
…
};
HealthCalcFunc defaultHealthCalc;
class GameCharacter{
public:
explicit GameCharacter (HealthCalFunc phcf = &defaultHealthCalc) : pHealthFunc (phcf){ }
int healthValue( ) const {return pHealthFunc ->calc(*this);}
private:
HealthCalcFunc* pHealthFunc;
};
class SlowHealthLoser: public HealthCalcFunc{ }
class FastHealthLoser: public HealthCalcFunc{ }
class B{
public:
void mf();
…
};
class D: public B{
public:
void mf(); //遮掩了B::mf
…
};
D x;
B* pB = &x;
pB->mf( ); //B::mf
D* pD = &x;
pD->mf( );//D::mf
non-virtual函数都是静态绑定。由于pB是一个pointer-to-B,通过pB调用的non-virtual永远是B所定义的版本,即使pB指向一个类型派生于B 的类型。
class Shape{ public: enum ShapeColor {Red, Green, Blue}; virtual void draw (ShapeColor color = Red) const = 0; … }; |
class Rectangle: public Shape{ public: virtual void draw (ShapeColor color = Green) const ; … }; |
class Circle: public Shape{ public: virtual void draw (ShapeColor color ) const ; //当客户以对象调用此函数,必须指定参数值 … }; |
Shape* ps; //静态类型为Shape*,没有动态类型
Shape* pc = new Circle; //静态类型为Shape*,动态类型为Circle*
Shape* pr = new Rectangle; //静态类型为Shape*,动态类型为Rectangle*
ps = pc//动态类型为Circle*
ps = pr//动态类型为Rectangle*
pc->draw(Shape::Red); //调用Circle::draw(Red)
pr->draw(Shape::Red);//调用Rectangle::draw(Red)
pr->draw( ); //调用Rectange::draw(Red)
但Rectange::draw缺省参数值是Green。原因:pr的静态类型为Shape*,所以此一调用的缺省参数值来自Shape class而非Rectangle class!
替代方法:
class Shape{ public: enum ShapeColor {Red, Green, Blue}; void draw (ShapeColor color=Red) const { doDraw(color); } private: virtual void doDraw (ShapeColor color) const = 0; }; |
class Rectangle: public Shape{ public: … private: virtual void doDraw (ShapeColor color) const; … }; |
STL set的底层实现是平衡查找树,但每个元素需要的开销比较大。如果空间比速度重要时,能否采用其他设计?例如可以采用底层的linked list。
template<typename T>
class set:public std::list<T>{ … }//错误做法,公有继承,即is-a关系。如果D是一种B,那么对B为真的每一件事对D也都应该为真。但list可以包含重复元素,set不可以。因此不是is-a关系。但set可以根据list实现出来。因此是has-a的关系。
template<typename T>
class set{
…
private: std::list<T> rep;
}
条款32认证过C++如何将public继承视为is-a关系。其中,class Student以public形式继承class Person,于是编译器在必要时刻将Student暗自转换为Person。但对于以下情形:
class Person { … }
class Student : private Person { … }
void eat( const Person& p);
Person p;
Student s;
eat ( p ); //OK
eat( s ); //error, 对于private继承,编译器不会自动将一个derived class对象转换为一个base class对象。
另外,由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们在base class中原本是protected或public属性。
如果你让class D以private形式继承class B,你的用意是为了采用class B内已经备妥的某些特性,不是因为B对象和D对象存在有任何观念上的关系。
Private继承意味is-implemented-in-terms-of(根据某物实现出),复合的意义也是这样。如何取舍?答案很简单:尽可能使用复合,必要时才使用private继承。
激进情况涉及空间最优化,可能会促使你选择“private继承”而不是“继承加复合”。这种激进情况只适用于你所处理的class不带任何数据时,这样的classes没有non-static成员变量,没有virtual函数,也没有virtual-base-class。例如:
class Emtpy { } //没有数据,所以其对象应该不使用任何内存 class HoldsAnInt { private: int x; Empty e; //应该不需要任何内存,但在大多数编译器中sizeof(Emtpy) =1。面对大小为0的独立对象,通常C++官方勒令默默安插一个char到空对象内。然而齐位需求可能使得编译器为HoldsAnInt这样的class加上一些衬垫。 }; |
Class HoldsAnInt : private Emtpy { private: int x; }; 几乎可以确定sizeof(HoldsAnInt) = sizeof(int). 这就是所谓的空白基类最优化EBO。(若客户非常在意空间,那么值得注意EBO。另外EBO一般只在单一继承下才可靠) |
现实中的empty class并不真的是 empyt。虽然它们从末拥有non-static成员变量,但往往内含typedefs, enums, static成员变量,或non-virtual函数。
当你面对并不存在is-a关系 的两个classes,其中一个需要访问另一个的protected成员,或需要重新定义其一或多个virtual函数,private继承极有可能成为正统设计策略。
class BorrowItem{ public: void checkOut( ); … }; |
class ElectronicGadget{ private: void checkOut( ); … }; |
class MP3Player: public BorrowItem, public ElectronicGadget {… }; |
MP3Player mp;
mp.checkOut( );//歧义,尽管checkOut只有BorrowItem类的可用(ElectronicGadget的checkOut私有)。这与C++解析重载函数调用的规则相符:在看到是否有函数可用之前,C++首先确认这个函数对此调用是否最佳匹配。找出最佳匹配之后才检验其可用性。
使用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们体积大,访问virtual base class的成员变量,也比访问non-virtual base class的成员变量慢。另外,支配virtual base class初始化的规则比non-virtual base的情况复杂且不直观 。
忠告:(1)非必要不使用virtual bases。平常使用non-virtual继承。(2)如果必须使用virtual base class,尽可能避免在其中放置数据。这样就不用担心这些classes身上的初始化和赋值带来的诡异事情了。