如果你令class D(“Derived”)以public形式继承class B(“Base”),你便是告诉C++编译器(以及你的代码读者)说,每一个类型为D的对象同时也是一个类型为B的对象,反之不成立。你的意识是B比D表现出更一般化的概念,而D比B表现出更特殊化的概念。你主张“B对象可派上用场的任何地方,D对象一样可以派上用场”,因为每一个D对象都是一种B对象。反之如果你需要一个D对象,B对象无法效劳,虽然每个D对象都是一个B对象,反之并不成立。
每个学生都是人,但并非每个人都是学生,大概就是这种关系吧。
但这种关系就容易设计出奇怪的关系,比如鸟会飞,企鹅是鸟,但企鹅不会飞。如果你的鸟类有fly()的话,那你的企鹅类就会产生问题了!当然我们可以让其在调用企鹅的fly的时候抛出异常,这样就能使程序在运行期发生错误,但更好的设计是在编译器发生错误,即鸟不声明fly函数!
这就是public继承,当我们以public模塑base class和dervied class关系时,能够施行与base class对象身上的每件事,也可以在dervied class施行。编译器能通过编译,但是这并不一定代表程序行为一定正确(逻辑错误)
请记住:
“public继承”意味着 is-a,适用于base classes身上的每一件事情一定适用于derived classes身上,因为每一个derived class对象也都是一个base class 对象。
C++的内层作用域的名称会遮掩外围作用域的名称。编译器会从local作用域开始查找,如果找到就不再找其他作用域(遮蔽),如果找不到就会找其外围作用域,直到往golbal作用域找去。
那么先看看这一段代码,当基类重载了mf1和mf3,并且添加了一个新版的mf3到Derived去,此时!
base class的所有名为mf1和mf3的函数都被derived class内的mf1和mf3函数遮掩掉了,从名称查找的观点来看,Base::mf1和Base::mf3不再被Derived继承了!!!!
#includeusing namespace std; 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 x) { { cout << "Base mf3("< public: virtual void mf1() { cout << "Derived mf1()" << endl; } void mf3() { cout << "Derived mf3()" << endl; } void mf4() { cout << "Derived mf4()" << endl; } }; int main() { Derived d; int x = 10; d.mf1();//Derived mf1() //d.mf1(x);//错误,因为dervied::mf1遮掩了Base:mf1 d.mf2();//Base mf2() d.mf3();//Derived mf3() //d.mf3(x);//错误,因为dervied::mf3遮掩了Base:mf3 d.mf4();//Derived mf4() return 0; }
因为你没有继承重载函数,使得base和derived classes之间不是is-a关系
但可以用using声明式达成目标:
#includeusing namespace std; 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 x) { { cout << "Base mf3("< public: using Base::mf1;//让base class内名为mf1和mf3的所有东西在derived的作用域内可见。 using Base::mf3; virtual void mf1() { cout << "Derived mf1()" << endl; } void mf3() { cout << "Derived mf3()" << endl; } void mf4() { cout << "Derived mf4()" << endl; } }; int main() { Derived d; int x = 10; d.mf1();//Derived mf1() d.mf1(x);//Base mf1(int) d.mf2();//Base mf2() d.mf3();//Derived mf3() d.mf3(x);//Base mf3(10) d.mf4();//Derived mf4() return 0; }
有时候你不想继承base classes的所有函数,那你可以使用private继承。
#includeusing namespace std; 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 x) { { cout << "Base mf3("< public: virtual void mf1() //转交函数 { Base::mf1(); //暗自成为inline } }; int main() { Derived d; int x = 10; d.mf1();//Derived::mf1() d.mf1(x);//错误! Base::mf1被遮掩了 return 0; }
请记住:
derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。
为了让被遮掩的名称再见天日,可使用using声明式或转交函数
public继承有两个部分组成:函数接口继承和函数实现继承。
于是有以下几条性质:
1.成员函数的接口总是被继承。
纯虚函数必须被任何”继承了它们”的具象类重新声明,而且它们在抽象的class中通常没有定义。
2.声明一个pure virtual函数的目的是为了让derived classes只继承函数接口。
并且具有纯虚函数的类称为抽象类,他不能被new出来。
#includeusing namespace std; class Base { public: virtual void mf1() = 0; }; int main() { Base b = new b; //错误 return 0; }
3.声明简朴的impure virtual函数的目的,是让derived classes继承该函数的接口和缺省实现。
但是有可能会出现继承类忘记重写impure virtual函数了,导致发生惨案!
所以有一个好的设计是使得基类声明为pure virtual,并且给一份默认缺省实现。
#includeusing namespace std; class Base { public: virtual void mf1() = 0; }; void Base::mf1() { cout << "Base::mf1();" << endl; }//缺省行为 class A :public Base { public: virtual void mf1() { Base::mf1(); }//使用默认的实现 }; class B:public Base { public: virtual void mf1() { cout << "B::mf1()" << endl; }//自己重写实现 }; int main() { A a; B b; a.mf1();//Base::mf1(); b.mf1();//B::mf1() return 0; }
4.声明non-virtual函数的目的是为了令derived classes继承函数的接口及一份强制性实现。
请记住:
接口继承和实现继承不同,在public继承之下,derived classes总是继承base class的接口。
pure virtual函数只具体指定接口继承
简朴的impure virtual函数具体指定接口继承及缺省实现继承。
non-virtual函数具体指定接口继承以及强制性实现继承
1.使用non-virtual interface(NVI)手法,那是Template Method设计模式的一种特殊形式,它以public non-virtual成员函数包裹较低访问性的virtual函数
#includeusing namespace std; class Base { protected: virtual void DoFunc() { cout << "Base::DoFunc()" << endl; } public: virtual void Func() { DoFunc(); } }; class A :public Base { protected: virtual void DoFunc() { cout << "A::DoFunc()" << endl; } }; class B:public Base { public: virtual void DoFunc() { cout << "B::DoFunc()" << endl; } }; int main() { A a; B b; a.Func(); b.Func(); return 0; }
2.将virtual函数替换为“函数指针成员变量”,这是策略模式的一种分解表现形式
#includeusing namespace std; class Base; void defaultFuc(const Base&); class Base { public: typedef void (*DoFunc)(const Base&); Base(DoFunc func = defaultFuc):dofunc(func) {} void Func() const { dofunc(*this); } private: DoFunc dofunc; }; class A :public Base { public: A(DoFunc func = defaultFuc) :Base(func) {} }; class B:public Base { public: B(DoFunc func = defaultFuc) :Base(func) {} }; void funcA(const Base&) { cout << "FuncA" << endl; } void funcB(const Base&) { cout << "FuncB" << endl; } int main() { A a(funcA); B b(funcB); a.Func(); b.Func(); return 0; }
3.以trl::function成员变量替换virtual函数,因而允许使用任何可调用物搭配一个兼容于需求的签名式。这也是策略设计模式的某种形式。
#include#include using namespace std; class Base; void defaultFuc(const Base&); class Base { public: typedef std::tr1::function DoFunc; Base(DoFunc func = defaultFuc):dofunc(func) {} void Func() const { dofunc(*this); } private: DoFunc dofunc; }; class A :public Base { public: A(DoFunc func = defaultFuc) :Base(func) {} }; class B:public Base { public: B(DoFunc func = defaultFuc) :Base(func) {} }; void funcA(const Base&) { cout << "FuncA" << endl; } void funcB(const Base&) { cout << "FuncB" << endl; } int main() { A a(funcA); B b(funcB); a.Func(); b.Func(); return 0; }
4.将继承体系内virtual函数替换为另一个继承体系内的virtual函数。这是策略模式传统实现手法.
#include#include using namespace std; class Base; class DoFunc { public: virtual void dofuc(const Base&) { cout << "DoFuc::dofuc()" << endl; } }; class FuncA :public DoFunc { public: virtual void dofuc(const Base&) { cout << "FuncA::dofuc()" << endl; } }; class FuncB :public DoFunc { public: virtual void dofuc(const Base&) { cout << "FuncB::dofuc()" << endl; } }; DoFunc defaultFuc; class Base { public: Base(DoFunc* func = &defaultFuc):dofunc(func) {} void Func() const { dofunc->dofuc(*this); } private: DoFunc* dofunc; }; int main() { FuncA A; FuncB B; Base a(&A); Base b(&B); a.Func(); b.Func(); return 0; }
请记住:
virtual函数的替代方案包括NVI手法及策略设计模式的多种形式,NVI手法自身是一种特殊形式的模板方法设计模式。
将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。
trl::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式兼容”的所有可调用物。
就是指如果继承类重新定义了继承而来的non-virtual就 发生违反is-a的关系。
看如下代码 :
A x对象分别被Base*,A*取了指针并调用Func结果却不相同。
毕竟它们调用的都说同一个对象x的Func。但是行为却不一样
#includeusing namespace std; class Base { public: void Func() const { cout << "Base::Func()" << endl; } }; class A :public Base { public: void Func()const { cout << "A::Func()" << endl; } }; int main() { A x; Base* pb = &x; A* pa=&x; pb->Func(); pa->Func(); return 0; }
请记住:
绝对不要重新定义继承而来的non-virtual函数
在C++中缺省参数值是静态绑定的。因为如果缺省值是动态绑定的话,编译器就必须有某种方法在运行期为virtual函数决定实当的参数缺省值。这比目前实行的”在编译器决定”的机制更慢而且更复杂。为了程序的执行速度和编译器实现上的简易度,C++做了这样的取舍。
A的Func(int x=1)的缺省值是1
而Base的缺省值是0
当执行pa->Func()的时候结果是A::Func() 0
#includeusing namespace std; class Base { public: virtual void Func(int x=0) const { cout << "Base::Func() "<< x << endl; } }; class A :public Base { public: virtual void Func(int x=1)const { cout << "A::Func() "<< x << endl; } }; int main() { Base* pa=new A; pa->Func(); return 0; }
请记住:绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数–你唯一应该覆写的东西—却是动态绑定
条款三十八:通过复合塑模出has-a或“根据某物实现出”复合是类型之间的一种关系,比如人有电话号码和家庭住址。
这种关系意味着has-a(有一个),通常用于处理两个不同领域。
#includeusing namespace std; class PhoneNum { public: PhoneNum(int num) :num(num) {} int getNum()const { return num; } private: int num; }; class Address { public: Address(string adr) :adr(adr) {} string getAdr()const { return adr; } private: string adr; }; class Person { public: Person(PhoneNum pn,Address adr):pn(pn),adr(adr) {} void PrintInfo()const { cout << "PhoneNum="<< pn.getNum()<<" Address="< PhoneNum pn(123456789); Address adr("地球"); Person p(pn, adr); p.PrintInfo(); return 0; }
另外,像stl中的Set内部也是用到了list,Set的函数成员大量依赖list及标准程序库其他部分提供的机能来完成,即根据list实现出set
请记住:
复合的意义和public继承完全不同。
在应用域,复合意味has-a(有一个)。在实现域,复合意味is-implemented-in-terms-of(根据某物实现出)
如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象。由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们在base class中原本是protected或public属性。
private继承意味implemented-in-terms-of(根据某物实现出)。如果D以private形式继承B,意思是D对象根据B对象实现 ,这在软件”设计”层面毫无意义,其意义只及于软件实现层面。
这和条款三十八说的复合的概念很像。但推荐尽可能使用复合。何时才用private?主要是当protected成员或virtual函数牵扯进来的时候。其实还有一种激进情况,那是当空间方面利害关系足以推翻private继承的支柱时。
假设我们需要用一个定时器去定时查看widget的数据。
我们有一个Timer类可以每秒执行Ontick函数,我们只需要让widget重新定义Ontick就行了。
但总不能public继承,毕竟这不是is-a的关系,很容易造成用户误用,所以我们以private继承Timer
#include#include #include using namespace std; class Timer { public: void Start() { while (true) { Sleep(1000); OnTick(); } } virtual void OnTick()const { cout << "Timer::OnTick()" << endl; } }; class Widget:private Timer { public: Widget() { Start(); } private: virtual void OnTick()const { cout << "OnTick()==>Widget INFO" << endl; } }; int main() { Widget w; return 0; }
这是一个好设计,但private并非绝对必要,我们可以用复合取而代之。
#include#include #include using namespace std; class Timer { public: void Start() { while (true) { Sleep(1000); OnTick(); } } virtual void OnTick()const { cout << "Timer::OnTick()" << endl; } }; class Widget { private: class WdigetTimer :public Timer { public: WdigetTimer() { Start(); } virtual void OnTick()const { cout << "OnTick()==>Widget INFO" << endl; } }; WdigetTimer timer; }; int main() { Widget w; return 0; }
然后就是private继承不会产生额外的开销。
我们都知道一个空类对象也会被强行塞一个char到空对象内。
但如果private继承的话,会有EBO(empty base optimilazation 空白基类最优化)。
#includeusing namespace std; class B { }; class A:private B { int x; }; class C { int x; B b; }; int main() { cout << sizeof(A) << " " << sizeof(int)<<" "< 请记住:
条款四十:明智而审慎地使用多重继承
private继承意味is-implemented-in-terms-of(根据某物实现出)。它通常比复合的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,很重要!钻石继承以及解决方案。virtual base class(virtual继承)
可以参考我写的这一篇
private继承意味着implement-in-terms-of(根据某物实现出)。
public inheritance(公开继承)意味着“is-a”(是一种)的关系。所以我们应该public继承自接口,private继承自实现
请记住:
多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要。
virtual 继承会增加大小,速度,初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具有实用价值的情况。
多重继承的确有正当用途。其中一个情节涉及public 继承某个interface class 和private继承某个协助实现的class的两相组合



