您的位置:首页 > 移动开发 > Objective-C

Conclusion for Inheritance and Object Oriented Design

2015-12-06 13:19 579 查看
条款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();
}
//...
};


条款34

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::draw
5.声明简朴的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
Derived
2.任何一个对象可能表现出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;
}
输出:

Widget
6.可以用复合来代替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 member
3.钻石型多重继承:有一个继承体系而其中某个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成员变量。

4.如果不想有上述重复情况,必须令那个带有此数据(File)成为一个virtual base class。这样,必须令直接继承它(File)的classes(InputFile和OutputFile)采用“virtual继承”

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。

6.使用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们体积大,访问virtual base classes的成员变量时,也比访问non-virtual base classes的成员变量速度慢。(使用virtual base classes,经可能避免在其中放置数据)
7. 下面补充一个关于virtual函数和多重继承的隐晦错误
#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++内置的类型转换函数,而不要强行转换
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: