Effective C++读书笔记(24)
来源:互联网 发布:java飞机大战源码 编辑:程序博客网 时间:2024/05/21 22:25
条款36:绝不重新定义继承而来的non-virtual函数
Never redefine an inherited non-virtualfunction
有如下代码:
class B {
public:
void mf();
...
};
class D: public B { ... };
虽然我们对B,D或mf一无所知,但面对一个类型为D的对象x:
D x;
B *pB =&x; // 获得一个指针指向x
pB->mf(); // 经由该指针调用mf
D *pD =&x; // 获得一个指针指向x
pD->mf(); // 经由该指针调用mf
两种情况中,pB和pD都调用了对象x中的成员函数mf。因为同样的函数和同样的对象,它们的行为应该相同。是的,但是也可能不。更明确地说,如果 mf 是non-virtual而 D 定义了它自己的mf版本:
class D:public B {
public:
void mf(); // hides B::mf
...
};
pB->mf();// calls B::mf
pD->mf(); // calls D::mf
造成这一两面行为的原因是,non-virtual函数如B::mf 和D::mf都是staticallybound(静态绑定)的。这就意味着因为 pB被声明为pointer-to-B类型,所以通过pB调用的non-virtual函数永远是B所定义的版本,即使pB指向一个类型为B的派生类的对象,一如本例。
在另一方面,virtual函数是 dynamically bound(动态绑定)的,所以它们不会发生这个问题。如果 mf是一个 virtual函数,无论通过pB还是pD调用mf都将导致D::mf的调用,因为 pB 和 pD真正指向的都是一个类型为D的对象。
如果你在编写class D且重定义了一个从class B继承到的non-virtual函数mf,D 的对象将很可能表现出不一致的行为。更明确地说,当mf被调用时,任何一个D对象的行为既可能像B也可能像D,而且决定因素与对象本身无关,但是和指向它的指针声明类型有关。引用也会展现和指针一样难以理解的行为。
再从理论上分析,public inheritance意味着 is-a,在类中声明一个non-virtual函数是为这个类建立起一个不变性,凌驾于特异性。因此:
l 每一件适用于B对象的事情也适用于D对象,因为每一个D对象都 is-a(是一个)D对象;
l B的派生类一定会继承mf的接口和实现,因为mf是B的non-virtual函数。
如果你要重新定义继承而来的non-virtual函数,则原本基类中的函数可以定义为virtual以求得不同的实现;而既然你是以public方式继承了基类,且那个函数不是virtual,就说明那个“不变性凌驾于特异性”的性质,即派生类可以直接使用基类中的那个non-virtual函数,且这样做是符合需求的设计。
我们以前已经解释了为什么多态基类中的析构函数应该是virtual。如果你违反了那个准则(如在一个多态基类中声明一个non-virtual析构函数),你也同时违反了本条款,因为派生类绝不应该重新定义一个继承而来的non-virtual函数(此处指基类的析构函数)。甚至对于没有声明析构函数的派生类,这也是成立的,因为如果你没有定义你自己的析构函数,编译器就会为你生成一个。
- 绝不要重新定义一个继承而来的non-virtual函数。
条款37:绝不重新定义继承而来的缺省参数值
Never redefine a function’s inheriteddefault parameter value
因为重新定义一个继承而来的non-virtual函数永远是错误的,所以我们将本条款的讨论限制在“继承一个带有缺省参数值的virtual函数”。
本条款成立的理由直接而明确:virtual函数是动态绑定(又名前期绑定)的,而缺省参数值是静态绑定(又名后期绑定)的。我们来回顾一下
对象的所谓静态类型,就是它在程序文本中被声明时所采用的类型。考虑这个类继承体系:
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;//缺省参数值与基类不同!
...
};
classCircle: public Shape {
public:
virtual void draw(ShapeColor color)const;
/*以对象调用此函数一定要指定参数值(静态绑定下此函数不从基类继承缺省参数值), 但以指针或引用调用此函数可以不指定参数值(动态绑定下此函数会从基类继承缺省参数值)*/
...
};
现在考虑这些指针:
Shape *ps;
Shape *pc = new Circle;
Shape *pr = new Rectangle;
在本例中,ps,pc 和 pr全被声明为 pointer-to-Shape类型,所以它们全都以此作为它们的静态类型,无论它们真正指向什么。
对象的所谓动态类型,是指目前所指对象的类型。也就是说,动态类型可以表现出一个对象将会有什么行为。在上面的例子中,pc的动态类型是 Circle*,pr的动态类型是Rectangle*。至于ps,它没有一个实际的动态类型,因为它尚未指向任何对象。动态类型,就像它的名字所暗示的,能在程序执行过程中改变(通常是经由赋值动作):
ps = pc;// ps的动态类型如今是Circle*
ps = pr; // ps的动态类型如今是Rectangle*
virtual函数系动态绑定而来,意味着当调用一个virtual函数时,究竟调用哪一份函数实现代码,取决于发出调用的那个对象的动态类型:
pc->draw(Shape::Red);// calls Circle::draw(Shape::Red)
pr->draw(Shape::Red); // calls Rectangle::draw(Shape::Red)
当考虑带有缺省参数值的virtual函数时,因为virtual函数是动态绑定的,但缺省参数是静态绑定的。这就意味着你可能在调用一个定义于派生类内的virtual函数的同时(动态),却使用了基类为它指定的缺省参数值(动态)。
pr->draw();// calls Rectangle::draw(Shape::Red)!
此例中,pr的动态类型是Rectangle*,Rectangle的virtual函数被调用,在 Rectangle::draw中缺省参数值是Green。然而pr的静态类型是 Shape*,这个函数调用的缺省参数值是从Shape中取得的,而不是Rectangle!结果就是一个调用由Shape和 Rectangle两个类中的draw声明式混合组成。
如果是引用,问题依然会存在。重点在于draw是一个virtual函数,而它的一个缺省参数值在派生类中被重定义。
C++坚持这种乖张的方式来运作的原因是为了运行时效率。如果缺省参数值是动态绑定的,编译器就必须提供一种方法在运行时确定适当的virtual函数参数缺省值,这比目前在编译期确定它们的机制更慢而且更复杂。最终的决定偏向了速度和实现简单这一边,而造成的结果就是享受高效运行的乐趣。
如果试着遵循本规则,为基类和派生类的用户提供同样的缺省参数值,又会造成代码重复。更糟的是,代码重复带来相依性:如果Shape 中的缺省参数值发生变化,所有重复给定缺省参数值的派生类必须同时变化。
当你要一个virtual函数按照你希望的方式运行有困难的时候,考虑可选的替代设计是很明智的。这里我们选择NVI(non-virtual interface idiom)手法:令基类内的一个public non-virtual函数调用private virtual函数,后者可被派生类重新定义。这里我们用non-virtual函数指定缺省参数,而private virtual函数做实际的工作:
classShape {
public:
enum ShapeColor { Red, Green, Blue };
void draw(ShapeColor color = Red) const// now non-virtual
{ doDraw(color); } // call virtual
...
private:
virtual void doDraw(ShapeColor color) const =0; // 真正工作在此处完成
};
classRectangle: public Shape {
public:
...
private:
virtual void doDraw(ShapeColor color) const; // 不需指定缺省参数值
...
};
因为non-virtual函数绝不应该被派生类覆写,这个设计很清楚地使得draw的color缺省参数值总是Red。
- 绝不要重新定义一个继承而来的缺省参数值,因为缺省参数值是静态绑定的,而 virtual 函数(你唯一应该覆写的东西)是动态绑定的。
- 《Effective C++》读书笔记(二)
- 《Effective C++》读书笔记(三)
- 《Effective C++》读书笔记(四)
- 《effective C++》读书笔记(一)
- 《effective C++》读书笔记(二)
- 《effective C++》读书笔记(三)
- <<Effective C++>>读书笔记(1)
- <<Effective C++>>读书笔记(4)
- iOS之《Effective Objective-C 2.0》读书笔记(24)
- 读书笔记之effective c++(一)
- 《Effective C++》读书笔记(四) 资源管理
- C++ 读书笔记 Effective C++(二)
- C++ 读书笔记 Effective C++(三)
- Effective C++(第三版)读书笔记
- 《Effective C++》读书笔记(第一部分)
- 《Effective C++》读书笔记(第二部分)
- Effective Object-c 2.0 读书笔记 (2)
- <<Effective C++>>读书笔记(2)
- 如何查看Linux系统中逻辑和物理cpu的个数
- zoj1879
- eclipse里的python环境怎么导入urllib模块
- DB2 Architect Baseline
- hibernate连接oracle 11g的有关问题
- Effective C++读书笔记(24)
- hibernate怎么插入DB时间
- Android 去除标题栏
- 常用的命令笔记1
- 淘宝开放平台
- How to use cuda-gdb
- Ajax应用常见的HTTP ContentType设置
- 动态绑定事件插件—livequery
- 安装软件备忘贴