读书频道 > 网站 > 网页设计 > 大规模C++程序设计
2.5 冗余包含卫哨
15-04-27    下载编辑
收藏    我要投稿   

本文所属图书 > 大规模C++程序设计

《大规模C++程序设计》由世界级软件开发大师John Lakos亲笔撰写,是C++程序设计领域最有影响力的著作之一。作者结合自己多年从事大规模C++项目的开发经验,详细介绍了大规模C++程序设计涉及的一系列概念、理论、立即去当当网订购

现实并不总是美好的,下面就是一个例子。理论上讲,唯一的内部包含卫哨就足够了。但是,对于大型项目而言,若不做进一步的考虑,成本可能会很大。

一个精心设计的系统由多层抽象构成。只要可能,我们总是希望先创建少量的基本对象类型,然后在更高的抽象层次上将这些对象组织成新的对象。一个科学应用可以将各种不同种类的原子建模为类。在元素周期表中有100多种原子。以各种方式和比例对这些相对较少的基本类型的实例进行组合(通过分层),可以创建宇宙中各种不同类型的分子。

此类分层设计的另一个例子是面向对象的窗口系统。假设有一个包含N个基本窗口部件(如按钮、刻度盘、滑动开关、显示控件等)的集合。为简洁起见,我们将这些基本窗口部件类分别命名为W1,W2,…,Wn。每个窗口部件Wi存在于自己的单个编译单元wi.c中,wi.c带有相应的头文件wi.h。我们通过组合各种类型的部件对象来创建新的界面类型,并将这M个界面类称为S1,S2,…,Sm,每一个类Si都存在于带有头文件si.h的编译单元中。

通常,每个界面都会使用数量可观的可用小部件。为便于本节论述,假设每个界面类型使用所有(或大部分)基本类型,这促使实现者在每个si.h文件中包含所有的w1.h,w2.h,…,wn.h文件。如图2-7显示了一个典型的界面头文件s13的内容。


 

你看到潜在的问题了吗?让我们继续论述。假设你已经开发了许多界面,并且在系统的某个编译单元ck.c中,需要包含所有的界面的头文件(也就是说创建它们)。一个窗口应用程序的包含图如图2-8所示,该图有N=5个小部件和M=5个界面。



预处理器看到ck.c已包含s1.h,也会包含w1.h至w5.h。当遇到s2.h时,在寻找结束符#endif的整个过程中,每个部件的头文件仍然必须重新打开并逐行进行处理(只要发现无须完成什么就可以了)。这种冗余的预处理对于s3.h、s4.h以及s5.h都会发生。尽管该程序也能被编译并正常地工作,但是在只用5个部件头文件就能工作的情况下,我们却不得不等候处理25个部件头文件。

除非已经严格采取措施进行预防,否则C++会倾向于拥有大型的、高密度的包含图(远远超过C)。尽管继承和分层有助于这个问题的解决,但是问题的根本在于部分C++开发者常常错误地认为,头文件中包含用户可能需要的所有头文件,多少对用户有所帮助。

避免密集的包含图是“隔离”主题的一部分,于第6章介绍。下面介绍一种即使在不良设计中也可以将这种再收敛包含的影响降到最低的实践。注意,某些开发环境足够智能,可以跟踪了解先前包含的头文件的情况,但是一般的环境是不能够追踪的。考虑可移植性,确保万无一失要比将来后悔好得多。

次要设计规则

在每个头文件的预处理器包含指示符周围放置冗余的(外部的)包含卫哨。

在每一个出现在头文件中的预处理器包含指示符周围放置一个冗余的(外部的)包含卫哨,这项技术应用于一个典型的界面头文件时,如图2-9所示。首次处理文件s13.h时,仍然导致w1.h,w2.h,…,wn.h被包含。但是,包含另一个界面时,不会引起小部件头文件任何冗余的解析。


 

注意,对于数学标准库的头文件,冗余包含卫哨与其余标准库的头文件不同。尽管math.h确实有自己的内部包含卫哨,但是它可能不遵循我们的标准。不同编译器的运行时库,很可能对所使用的包含卫哨有不同的命名规则,命名规则不可能总是一致的。由第三方供应商提供的组件还可能使用另一种规则。对于所有不能保证遵守我们的包含卫哨命名规则的组件,有必要在相应的包含指示符之后(如math.h中使用的那样)添加一行,用以定义适当的包含卫哨符号。

使用冗余包含卫哨确实令人不快。现在,在一个头文件中包含一个文件头,不只需要一行,而是需要至少三行——如果包含的头文件不是我们自己定义的,则需要四行。冗余包含卫哨不仅让写头文件花费更长时间,而且让头文件更难阅读。使用冗余包含卫哨也需要遵守一致的和可预知的命名规则。这值得吗?

具有密集包含图的真正的大型项目经验表明,对上述问题要响亮回答YES,最初,由数百万行C++源代码组成的项目的构建,使用工作站的一个大型网络进行编译需要花费一周的时间。插入冗余包含卫哨可以显著地降低编译时间,且代码无需实质性改变。

我们刚刚论述的内容,基本上对于小型甚至中等规模的系统都不是问题。但是,如果我们正在处理的系统相当于数百个小部件、数百个基本界面时,会怎么样?为提供量化信息证明使用冗余包含卫哨的好处,我尝试了以下的实验。

设组件和界面的数量均为N,然后生成子系统并且对单个编译单元测量编译时间(该编译时间主要是C预处理器时间),这些编译单元包括所有带有和不带有冗余包含卫哨的界面头文件。我尝试对带有10行内容的头文件和带有100行内容的头文件进行实验,定义加速因子为:不带有冗余包含卫哨的编译时间除以带有冗余包含卫哨的编译时间。结果如图2-10所示。


 

对于少于8个小部件和8个界面的系统,要么不存在加速,要么加速很小。给定的总编译时间小于1秒CPU时间,则几乎没有影响。

C++中的头文件很少只有10行,100行的也还算是小的,但是比较典型。对于带有32个小部件的系统,在我的机器上编译一个客户端组件花费在C预处理器的平均时间可以以超过6的加速因子降低(从5.8CPU秒到0.9CPU秒)。对于带有64个小部件的系统,加速因子超过11!冗余包含卫哨很难看,但是没有实质性的危害。不使用冗余卫哨要冒编译时间复杂度达到二次方(即)的风险。

注意,冗余卫哨在.c文件中不是必要的。如果在.c文件中未能谨慎地复制#include指示符,对于N个不同的.h文件,(病态的)最坏情况下的性能是2N,问题规模仍然是线性的(即O (N))。

本节中的数据是对运行在基于UNIX工作站上的CFRONT的反映。其他开发环境可能有稍微不同的特性。在第6章中,我们将了解到在头文件中嵌入#include指令,那不仅是不可取的,而且经常是不必要的。就算不考虑性能上的提升,冗余包含卫哨的丑陋形式也能提醒我们,要尽量避免在头文件中放置#include指令。

点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:1.3 功能
下一篇:1.5 小结
相关文章
图文推荐
JavaScript网页动画设
1.9 响应式
1.8 登陆页式
1.7 主题式
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站