条款32:
1.公有继承是一种is-a关系
2.任何函数如果期望获得一个类型为基类(pointer-to基类或reference-to基类)的实参,都也愿意接受一个继承类对象。这点只对public继承成立。
3.如果解决public继承中“不是所有的鸟都会飞”问题?
4.某些可施行于矩阵身上的事情却不可以施行于正方形身上(故不可以public继承)
条款33:
1.内层作用域的名称会掩盖外层作用域的名称(编译器先在local作用域查找,找不到再去其他作用域)
2.继承类的作用域被嵌套在基类作用域内,所以继承类内的名称会覆盖基类名称。
3.继承类内的函数名与基类内函数名相同,类型不同的重载函数,继承类内的函数会遮掩这个函数。
4.如果继承基类并且加上重载函数,又希望重新定义或覆写其中一部分,继承类遮掩了基类内的同名重载函数,可以使用using声明式达成目标。(using声明式放在public区域:bass class内的public名称在publicly dereved class内应该是public)
5.如果是私有继承,using声明式派不上用场,using声明式会令继承而来的某给定名称之所有同名函数在derived class中都可见。
6.如果不想继承base class所有函数,private derived可以运用转交函数来遮掩base class带参数的重载函数,不需要使用using声明式。
class Base{ public: virtual void mf1() = 0; virtual void mf1(int); //... }; class Derived :private Base{ public: virtual void mf1() { Base::mf1(); } //... };
1.拥有纯虚函数的基类是一个抽象类,只能被继承,不能创建基类实体,只能创建derived class的实体。
2.public继承由两部分组成,函数接口继承和函数实现继承。其中函数的接口就是声明。
3.纯虚函数就是只有声明,没有实现,是接口继承。任何“继承了”他们的具象class必须重新声明,而且他们在抽象class中通常没有定义。声明一个纯虚函数是为了让derived class只继承函数接口。
4.可以为纯虚函数提供定义,但调用它的唯一途径是“调用时明确指出其class名称”。
Shape* ps = new Shape; //错误,Shape是抽象类 Shape* ps1 = new Rectangle; //OK ps1->draw(); //调用Rectangle::draw Shape* ps2 = new Ellipse; //OK ps2->draw(); //调用Ellipse::draw ps1->Shape::draw(); //调用Shape::draw ps2->Shape::draw(); //调用Shape::draw5.声明简朴的impure virtual函数的目的,是让继承类继承该函数的接口和缺省实现。即非纯虚函数能同时指定函数声明和函数缺省行为。
6.允许impure virtual函数同时指定函数声明和函数缺省行为有可能造成危险。(某些不需要该缺省行为的继承类也拥有了)
7.解决6中问题的方法是切断“virtual函数接口”和其“缺省实现”之间的连接。
class Airplane{ public: virtual void fly(const Airport& destination) = 0; //... protected://因为是Airplane及其继承类的实现细目。乘客不需要知道怎么飞 void defaultFly(const Airport& destination); }; void Airplace::defaultFly(const Airport& destination) { //缺省行为,将飞机飞至指定目的地 }这次纯虚函数只提供接口,缺省行为由独立函数defaultFly提供。若想使用缺省实现,可以在其fly函数中对defaultFly做一个inline调用。
这样继承类就不可能意外继承不正确的fly实现代码了。
8.上一条中过度雷同的杉树名称可能引起class命名空间污染问题。下面利用“pure virtual函数必须在derived class中重新声明,但他们也可以拥有自己的实现”这一事实。
class Airplane{ public: virtual void fly(const Airport& destination) = 0; //... }; void Airplace::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); }; void ModelB::fly(const Airport& destination) { //将B型飞机飞至指定目的地 }
9.声明non-virtual函数的目的是为了令derived classed继承函数的接口以及一份强制实现。
条款35:
见条款35(考虑virtual函数以外的其他选择)之Template Method模式和Strategy模式
条款36:
1.non-virtual函数是静态绑定的,virtual函数是动态绑定的。
#include <iostream> using namespace std; class B{ public: void mf(){ cout << "Base" << endl; } //... }; class D :public B{ public: void mf(){ cout << "Derived" << endl; } }; int main() { D x; B* pB = &x; D* pD = &x; pB->mf(); pD->mf(); return 0; }输出是:
Base Derived原因是pB被声明为一个pointer-to-B,通过pB调用的non-virtual函数永远是B所定义的版本,即使pB指向一个类型为B派生的类的对象。
如果mf是个virtual函数,不论是通过pB或pD调用mf,都会导致调用D::mf,因为pB和pD真正指的都是一个类型为D的对象。
#include <iostream> using namespace std; class B{ public: virtual void mf(){ cout << "Base" << endl; } //... }; class D :public B{ public: void mf(){ cout << "Derived" << endl; } }; int main() { D x; B* pB = &x; D* pD = &x; pB->mf(); pD->mf(); return 0; }输出是:
Derived Derived2.任何一个对象可能表现出B或D的行为,决定因素在于“指向该对象的指针”当初的声明类型。
条款37:
1.virtual函数是函数动态绑定,而缺省参数值却是参数静态绑定,非virtual函数是函数静态绑定。(静态绑定又名前期绑定,动态绑定又名后期绑定)
2.静态类型是它在程序中被声明时所采用的类型,所谓动态类型是指“目前所指对象的类型”。
#include <iostream> using namespace std; class Shape{ public: enum ShapeColor{ Red, Green }; virtual void draw(ShapeColor color = Green) const = 0 { cout << "noColor" << endl; } }; class Rectangle :public Shape{ public: virtual void draw(ShapeColor color = Red) const { cout << (color==Green?"Green":"Red") << endl; } }; class Circle :public Shape{ public: virtual void draw(ShapeColor color) const { cout << (color == Green ? "Green" : "Red") << endl; } }; int main() { Shape* pc=new Circle; pc->draw(); //使用Shape的缺省参数Green Shape* pr = new Rectangle; pr->draw(); //使用Shape的缺省参数Green pr->draw(Shape::Red); //不使用缺省参数 Rectangle* r = new Rectangle; r->draw(); //使用Rectangle的缺省参数Red return 0; }
输出是:
Green Green Red Red注意:缺省情况下调用的函数是继承类的,但是默认参数是基类提供的。
3.缺省参数值是静态绑定的,这样编译器就可以在编译期决定,这样可以提高运行期效率。
4.使用条款35的NVI手法可以避免这种情况:令base class 内的一个public non-virtual函数调用private virtual函数,后者可以被derived classes重新定义。这里我们让non-virtual函数指定缺省参数,而private virtual函数负责真正的工作。
class Shape{ public: enum ShapeColor{Red,Green,Blue}; void draw(ShapeColor color = Red) const { doDraw(color); } private: virtual void doDraw(ShapeColor color) const; }; class Rectangle :public Shape{ public: //... private: virtual void doDraw(ShapeColor color) const; };
条款38:
1.复合关系是指某种对象内含有它中类型的对象,复合意味着has-a或is-implemented-in-terms-of。
2.人、汽车、一张视频画面等属于应用域,缓冲区、互斥器、查找树等属于实现域。当复合发生在应用域内的对象之间表现出has-a关系;当它发生在实现域内,则是表现is-implemented-in-terms-of的关系。
注意:STL中vector与list的关系就是后者。
3.如何根据list实现set。
条款39:
1.protected成员:对外界来说,行为与私有成员相似,对派生类来说,行为与公有成员相似。
2.如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象。
3.由private base class继承而来的所有成员,在dervied class中都会变成private属性。纵使他们在基类中原本是protected或public属性。
4.private继承意味着implemented-in-terms-of,private继承只是一种实现技术,private base class的每样东西对继承类来说都只是实现枝节而已。
5.基类的virtual函数经过private继承后,在dervied class中都会变成private属性,如果重定义在dervied class的public下,则会提供public接口,仍然可以运行,但是不要这样做,这样会让客户端以为他们可以调用它。
#include <iostream> using namespace std; class Timer{ public: explicit Timer(int tickFrequency){} virtual void onTick() const{ cout << "Timer" << endl; } //... }; class Widget :private Timer{ public: Widget() :Timer(1) {}/* void testOnTick() { onTick(); }*/ virtual void onTick() const{ cout << "Widget" << endl; } }; int main() { Widget test; test.onTick(); return 0; }输出:
Widget6.可以用复合来代替private继承,采用嵌套class,阻止继承类重新定义onTick()。
class Widget{ private: class WidgetTimer :public Timer{ public: virtual void onTick() const; //... }; WidgetTimer timer; //... };7.上面那种方法可以模拟Java和C#的“阻止derived class重新定义virtual函数”。
8.如果是继承,则必须包含头文件,如果是复合,则只需前置声明。所以使用复合可以降低编译依存性。
9.private主要用于“当一个意欲成为继承类者想访问一个意欲成为基类的protected成分,或为了重新定义一个或多个virtual函数。”
10.当所处理的class不带任何数据时(没有non-static成员变量,没有虚函数,也没有virtual base class),选择private继承而不是“继承加复合”,可使空间最优化。
#include <iostream> using namespace std; class Empty{}; class HoldsAnInt1{ private: int x; Empty e; }; class HoldsAnInt2:private Empty{ private: int x; }; int main() { cout << sizeof(Empty) << endl; cout << sizeof(int) << endl; cout << sizeof(HoldsAnInt1) << endl; //大于sizeof(int) cout << sizeof(HoldsAnInt1) << endl; //大多数编译器等于sizeof(int) return 0; }
输出结果:
1 4 8 8(注:我的VS2013输出8,大多编译器输出4)
这就是所谓的空白基类最优化在(emptybase optimization-EBO 或 empty base classopimization-EBCO)。在空基类被继承后由于没有任何数据成员,所以子类优化掉基类所占的1byte
11.上面空表内占的内存为1字节,因为面对“大小为零的独立对象”,通常C++官方勒令默默安插一个char到空对象内。
12.STL有许多技术用途的empty classes,其中内含有用的成员(通常是typedefs),包括基类unary_function和binary_function,这些是“用户自定义的函数对象”,通常会被继承的classes。
条款40:
1.如何进行多重继承?
#include <iostream> using namespace std; class BorrowableItem{ public: void checkOut(){ cout << "BorrowableItem" << endl; } //... }; class ElectronicGadget{ private: //虽然是private,但两个checkOut有相同的匹配程度 bool checkOut() { cout << "ElectronicGadget" << endl; return 1; } //... }; class MP3Player :public BorrowableItem, public ElectronicGadget //多重继承 {}; int main() { MP3Player mp; mp.checkOut(); //error:歧义,调用哪个checkOut return 0; }注:基类的private函数经过public继承后,对继承类来说,仍然是private。
2.如果程序从一个以上的base classes继承相同的名称(如函数、typedef等等),会导致歧义,如何解决?
可以在调用时明确调用那个checkOut函数。
mp.BorrowableItem::checkOut(); //OK mp.ElectronicGadget::checkOut(); //error:cannot access private member3.钻石型多重继承:有一个继承体系而其中某个base class和某个derived class之间有一条以上的相通线路。
class File{}; class InputFile :public File{}; class OutputFile :public File{}; class IOFile :public InputFile, public OutputFile {};C++的缺省做法是base class内的成员变量经由每一条路径被复制,IOFile从其每一个base class继承一份,所以其对象内应该有两份fileName成员变量。
class File{}; class InputFile :virtual public File{}; class OutputFile :virtual public File{}; class IOFile :public InputFile, public OutputFile {};5.C++标准程序库内含一个多重继承体系,结构和4相似,只不过class是class template,名称分别是basic_ios,basic_istream,basic_ostream和basic_iostream。
#include <iostream> using namespace std; class Base1{ public: virtual void foo1() {}; }; class Base2{ public: virtual void foo2() {}; }; class MI : public Base1, public Base2{ public: virtual void foo1 () {cout << "MI::foo1" << endl;} virtual void foo2 () {cout << "MI::foo2" << endl;} }; int main(){ MI oMI; Base1* pB1 = &oMI; pB1->foo1(); Base2* pB2 = (Base2*)(pB1); // 指针强行转换,没有偏移 pB2->foo2(); pB2 = dynamic_cast<Base2*>(pB1); // 指针动态转换,dynamic_cast帮你偏移 pB2->foo2(); return 0; }
你会认为屏幕上会输出什么?是下面的结果吗?
MI::foo1 MI::foo2 MI::foo2 |
这样认为没有什么不对的,因为C++的多态性保证用父类指针可以正确的找到子类实现,并调用。所以会有上面的输出。
但是,现实却不是这样,下面是真实的输出:
(以上实现在VC 2005和Linux Gcc 4.1.2效果一致)
为什么会出现上面的情况呢,上面代码中的注释部分也许解释了,这里再来详细的来分析一下。
首先,C++使用一种称之为vtable(google “vtable” for more details)的东西实现virtual函数多态调用。vtable每个类中都有一个,该类的所有对象公用,由编译器帮你生成,只要有virtual函数的类,均会有vtable。在继承过程中,由于类Base1和类Base2都有vtable,所以类MI继承了两个vtable。简单的分析一下对象oMI内存结构,如下:
0 vtable_address_for_Base1 –> [MI::foo1, NULL] 4 vtable_address_for_Base2 –> [MI::foo2, NULL] |
其实很简单,就两个vtable的指针,0和4代表相对地址,指针地址大小为4。
pB1的值为0(pB1 == 0),所以调用“pB1->foo1()”时,可以正确的找到MI::fool这个函数执行。
但是当使用强行转换,将pB1转给pB2,那么实质上pB2的值也是0(pB2 == 0),当调用“pB2->foo2()”时,无法在第一个vtalbe中找到对应的函数,但是却不报错,而是选择执行函数MI::foo1,不知道为什么会有这种行为,但是这种行为却十分恶心,导致结果无法预期的(最后调用的函数会与函数申明的循序有关),不太会引起注意,使得bug十分隐晦。
可以设想,当一个有复杂的业务逻辑的程序,而类似这种函数调用和指针强行转换分布在不同的函数或模块中,可想而知,bug定位十分困难。
当使用动态转换时,也就是“pB2 = dynamic_cast<Base2*>(pB1)”,dynamic_cast函数会根据尖括号中的类型进行指针偏移,所以pB2的值为4(pB2 == 4),这样调用“pB2->foo2()”就会按照期望的方式执行。
结论
上面的现象在单继承中是不会出现的,因为只有一个vtable(子类的virtual函数会自动追加到第一个父类的vtable的结尾)。所以不会出现上面的现象,而多重继承却出现了上面的想象,所以需要注意以下两点:
1)多重继承需要慎用
2)类型转换尽量采用c++内置的类型转换函数,而不要强行转换