== Part 3/4 ============================ =============================
■□ 第14节:程式风格指导
=============================
Q81:有任何好的 C++ 程式写作的标准吗?
感谢您阅读这份文件,而不是再发明自己的一套。
但是请不要在 comp.lang.c++ 里问这问题。几乎所有软体工程师,或多或少都把这
种东西看成是「大玩具」。而且,一些想成为 C++ 程式撰写标准的东西,是由那些
不熟悉这语言及方法论的人弄出来的,所以最後它只能成为「过去式」的标准。这种
「摆错位置」的现象,让大家对程式写作标准产生不信任感。
很明显的,在 comp.lang.c++ 问这问题的人,是想使自己更精进,不会因自己的无
知而绊倒,然而一些回答却只是让情况更糟而已。
========================================
Q82:程式撰写标准是必要的吗?有它就够了吗?
程式撰写标准不会让不懂 OO 的人变懂;只有训练及经验才有可能。如果它有用处的
话,那就是抑制住那些琐碎无关紧要的程式片段--当大机构想把零散的程式设计组
织整合起来时,这些片段常常会出现。
但事实上你要的不光是这种标准而已。它们提供的架构让新手少去担心一些自由度,
但是系统化的方法论会比这些好看的标准做得更好。组织机构需要的是一致性的设计
与实行“哲学”,譬如:强型别或弱型别?用指标还是参考介面? stream I/O 还是
stdio? C++ 程式该不该呼叫 C 的?反过来呢? ABC 该怎麽用?继承该用为实作的
技巧还是特异化的技巧?该用哪一种测试策略?一一去检查吗?该不该为每个资料成
员都提供一致的 "get" 和 "set" 介面?介面该由外往内还是由内往外设计?错误状
况该用 try/catch/throw 还是传回值来处理?……等等。
我们需要的是详细的“设计”部份的「半标准」。我推荐一个三段式标准:训练、谘
询顾问以及程式库。训练乃提供「密集教学」,谘询顾问让 OO 观念深刻化,而非仅
仅是被教过而已,高品质的程式库则是提供「长程的教学」。上述三种培训都有很热
门的市场景况。(【译注】无疑的,这是指美、加地区。)接受过上述培训的组织都
有如此的忠告:「买现成的吧,不要自己硬干 (Buy, Don't Build.)。」买程式库,
买训练课程,买开发工具,买谘询顾问。想靠自学来达到成功的工具厂商及应用/系
统厂商,都会发现成功很困难。
【译注】这一段十分具有参考价值。不过有些背景资料得提供给各位参考。别忘了:
作者是美国人,是以该地为背景,且留意一下他所服务的公司是做什麽的..
... :-) 唉!国内有这麽多的专业顾问公司吗? :- 「成员物件」
* 「型态签名」 --> 「型别」
* 「本体」 --> 「真正所属的类别」
这样子,你就看到虚拟资料的定义。
从另一个角度来看,就是把「各个物件」的成员函数与「动态」成员函数区分开来。
「各个物件」成员函数是指:在任何物件案例中,该成员函数可能会有所不同,可能
会塞入函数指标来实作出来;这个指标可以是 "const",因为它在物件生命期中不会
变更。「动态」成员函数是指:该成员函数会随时间动态地改变;也可能以函数指标
来做,但该指标不会是 const 的。
推而广之,我们得到三种不同的资料成员概念:
* 虚拟资料:成员物件的定义(真正所属的类别)可被子类别覆盖,只要它的宣告
(型别)维持不变,且此覆盖是子类别的静态性质。
* 各物件的资料:任何类别的物件在初始化时,可以案例化不同型式(型别)的成
员物件(通常是一个 "wrapper" 包起来的物件),且该成员物件真正所属的类别
,是把它包起来的那个物件之静态性质。
* 动态资料:成员物件真正所属的类别,可随时间动态地改变。
它们看起来都差不多,是因为 C++ 都不「直接支援」它们,只是「能做得出来」而
已;在这种情形下,模拟它们的机制也都一样:指向(可能是抽象的)基底类别的指
标。在直接提供这些 "first class" 抽象化机制的语言中,这三者间的差别十分明
显,它们各有不同的语法。
========================================
Q100:我该正常地用指标来配置资料成员,还是该用「成份」(composition)?
成份。
正常情况下,你的成员资料应该被「包含」在合成的物件里(但也不总是如此;
"wrapper" 物件就是你会想用指标/参考的好例子;N-to-1-uses-a 的关系也需要某
种指标/参考之类的东西)。
有三个理由说明,完全被包含的成员物件(「成份」)的效率,比自由配置物件的指
标还要好:
* 额外的间接层,每当你想存取成员物件时。
* 额外的动态配置("new" 於建构子中,"delete" 於解构子中)。
* 额外的动态系结(底下会解释)。
========================================
Q101:动态配置成员物件有三个效率因素,它们的相对代价是多少?
这三个效率因素,上一则 FAQ 有列举出来:
* 以它本身而言,额外的间接层影响不大。
* 动态配置可能是个效率问题(当有许多配置动作时,典型的 malloc 会拖慢速度
;OO 软体会被动态配置拖垮,除非你事先就留意到它了)。
* 用指标而非物件的话,会带来额外的动态系结。每当 C++ 编译器能知道某物件「
真正的」类别,该虚拟函数呼叫就能“静态”地系结住,能够被 inline。Inline
可能有无限大的 (但你可能只会相信有半打的 :-) 最佳化机会,像是 procedural
integration、暂存器生命期等等事项。三种情形之下,C++ 编译器能知道物件真
正的类别:区域变数、整体/静态变数、完全被包含的成员物件。
完全被包含的成员物件,可达到很大的最佳化效果,这是「成员物件的指标」所不可
能办到的。这也就是为什麽采用参考语意的语言,会「与生俱来」就效率不彰的原因
了。
注意:请读读下面三则 FAQs 以得到平衡的观点!
========================================
Q102:"inline virtual" 的成员函数真的会被 "inline" 吗?
Yes,可是...
一个透过指标或参考的 virtual 呼叫总是动态决定的,可能永远都不会被 inline。
原因:编译器直到执行时(亦即:动态地),才会知道该呼叫哪个程式,因为那一段
程式,可能会来自呼叫者编译过後才出现的衍生类别。
因此,inline virtual 的呼叫可被 inline 的唯一时机是:编译器有办法知道某物
件「真正所属的类别」之时,这是虚拟函数呼叫里所要知道的事情。这只会发生在:
编译器拥有真正的物件,而非该物件的指标或参考。也就是说:不是区域变数、整体
/静态物件,就是合成物件里的完全包含物件。
注意:inline 和非 inline 的差距,通常会比正常的和虚拟的函数呼叫之差别更为
显著。譬如,正常的与虚拟的函数呼叫,通常只差两个记忆体参考的动作而已,可是
inline 与非 inline 函数就会有一个数量级的差距(与数万次影响不大的成员函数
呼叫相比,函数没有用 inline virtual 的话,会造成 25X 的效率损失!
[Doug Lea, "Customization in C++," proc Usenix C++ 1990]).
针对此现象的对策:不要陷入编译器/语言厂商之间,对彼此产品的虚拟函数呼叫,
做永无止尽的性能比较争论(或是广告噱头!)之中。和语言/编译器能否将成员函
数呼叫做「行内展开」相比,这种比较完全没有意义。也就是说,许多语言编译器厂
商,拼命强调他们的函数分派方式有多好,但如果他们没做“行内”成员函数呼叫的
话,整体性能还是会很差,因为 inline--而非分派--才是最重要的性能影响因
素。
注意:请读读下两则 FAQs 以看看另一种说法!
========================================
Q103:看起来我不应该用参考语意了,是吗?
错。
参考语意是个好东西。我们不能抛弃指标,我们只要不让软体的指标变成一个大老鼠
窝就行了。在 C++ 里,你可以选择该用参考语意(指标/参考)还是数值语意(物
件真正包含其他物件)的时机。在大型系统中,两者应该取得平衡。然而如果你全都
用指标来做的话,速度会大大的降低。
接近问题层次的物件,会比较高阶的物件还要大。这些针对「问题空间」抽象化的个
体本身,通常比它们内部的「数值」更为重要。参考语意应该用於问题空间的物件上
。
注意:问题空间的物件,通常会比解题空间的更为高阶抽象化,所以相对地问题空间
的物件通常会有较少的交谈性。因此 C++ 给我们一个“理想的”解决法:我们用参
考语意,来对付那些需要独立的个体识别 (identity) 者,或是大到不适合直接拷贝
的物件;其他情形则可选择数值语意。因此,使用频率较高的就用数值语意,因为(
只有)在不造成伤害的场合下,我们才去增加弹性;必要时,我们还是选择效率!
还有其他关於实际 OO 设计方面的问题。想精通 OO/C++ 得花时间,以及高素质的训
练。若你想有个强大的工具,你必须投资下去。
>>>
========================================
Q104:参考语意效率不高,那麽我是否应该用传值呼叫?
不。
前面的 FAQ 是讨论“成员物件”(member object) 的,而不是函数参数。一般说来
,位於继承阶层里的物件,应该用参考或指标来传递,而非传值,因为惟有如此你才
能得到(你想要的)动态系结(传值呼叫和继承不能安全混用,因为如果把大大的子
类别物件当成基底的物件来传值的话,它会被“切掉”)。
除非有足以令人信服的反方理由,否则成员物件应该用数值,而参数该用参考传递。
前几则 FAQs 提到一些「足以信服的理由」,以支持“成员物件该用参考”一事了。
|