(WQ语,虽知“求全不若守缺”,可还是忍不住补完此专题文章,实在是因为太精彩了,太具启发了。)
对话#08:访问限制
轰。
巨响自远方传来。感觉上的难受更甚于听觉上的。挖掘队又开始工作了。
“我希望……”珍妮只开了个头就又没声了。
“我知道。”我埋头于工作中。过了好一会儿,我加了句:”我也希望能给家里发封信。但通讯系统必须很快恢复工作才行。”
“它们能工作,” 珍妮说道。
轰,又一声巨响。
“是的,但因为圆屋的系统已被破坏,它被限定为只在紧急事件时使用。我们现在没有被授权。这是合理的。”我随口答道。
又一阵的沉默。最后,珍妮低声道:”啊。好吧,他们是这么说的,而现在不是这种情况?”
“对。这只是暂时禁止。我也不喜欢这样,但你肯定不认为他们在……”现在是我没声了。
轰。
“轰”。
这就是粘在我的显示器上的便笺上所写的内容。是的,还有跟着一个文件名我知道这个文件-我稍前加到project中的一个工具类。便笺是用Guru特有的笔法写的,但我搞不懂它是什么意思。Guru不在,我无法问她。
就在此时,温迪,我隔间的程序员,进来并坐到她位置上。我探起头,期望于她能明白便笺的意思。她看起来很憔悴,头枕在手上。 “你看起来有个疯狂的周末,”我冒昧道。
她无力地点点头。”周五我回到家时,一氧化碳探测器在报警-煤气在泄漏。我们不得不在一个朋友家渡过周末,直到它修好。我几乎没有合眼-我一直在想,如果没装探测器的话会发生什么[1] 。”
“噢,真是千钧一发,”我同情道,并在纸上记下要仔细检查我的探测器。”每个人都好吧?”温迪点点头。我认为她需要分散一下思维,所以就向她提起这个便笺并问她是什么意思。
“等一下,”她答道,”Guru将纸条留在你显示器上?”我点点头。 “My friend,”她说,”听起来象你当了一回鲍勃。”
我惊呆了。”你在开玩笑吧!”我脸色发白。Bob was the worst programmer ever鲍勃一直是最差的程序员,我一有机会就攻击他和他差劲的编程风格。
温迪摇着她的头,一脸的严肃。 “没有。一直以来他是唯一收到这种神秘纸条的人,如果你也收到一个,你肯定做了一次坏孩子。让我们看一下源文件。”她转向计算机,登录并调出有问题的文件。
我搬过一把椅子。”在我看来,这儿一切都没问题,”我一边说一边从她肩后看过去。
温迪轻微地动了一下-她比我预料得更专注。”粗看之下没问题,”温迪同意。”也许和以前的版本比对一下会找到一些线索。”我注视着她使用版本控制系统显示两个版本间的区别。我注意到-我仍然在试图掌握最基本的checkin/checkout指令,而此时她立刻就调用了一个版本比对命令。我还在试图归类温迪念出的命令。
“有了,这而有些说法,但很不关键,”她指着屏幕说。”你更改了类的数据成员。”
我凑到屏幕前。 “是的,我小小地调整了其实现,我将标识对象位置的3个分开的double合并为一个double数组。它使得一些成员函数更具效率。但这些成员都是私有的,看到了吗?”
class Point
{
private:
double location[3]; // x, y, z coordinates.
public:
void setLocation(double x, double y, double z) {
location[0]=x;
location[1]=y;
location[2]=z;
}
void getLocation(double &x, double& y, double &z) {
x=location[0];
y=location[1];
z=location[2];
}
};
就在这时,我注意到比较器显示文件顶部的一个空行被修改了。”为什么显亮这一行?”我嚷起来。
“也许是空格变化,”温迪猜测。”那么,让我们设置为忽略空格。”在一些击键动作后,于是……这一行仍然处于显亮状态。我将光标定位到这一行,并按下”End”键-骇人听闻的事情出现了。缩进到最右边的出乎常理地,是这样一条语句:
#define private public
温迪和我互相看了一下。. “鲍勃!”我们齐声道。
鲍勃,就在此时,正向我们走来。Guru不是唯一”说曹操就曹操到”的人。”嗨,新来的,”他说道,”我正巧路过,就来看一下你是否已经改好了问题。”
我闭上眼并数十。慢慢地。然后,又数回零。我举起便笺:”你说的是这个问题?”我轻快地问道。
“嗨。是的,就是这个。她莫名其妙地将这个留在我的显示器上。当你将x、y和z改到数组中时,它搞砸了我大部分代码,所以我把它传给了你。改掉它,从现在起再别乱搞了,可以吧,新来的。”
“鲍勃,这个#define语句是什么意思?”我指着那行讨厌的代码。
“哦这个,”鲍勃呵呵笑了。 “很酷的,不是吗?我的代码需要直接访问相关数据。高效,, y'know你知道的。起先,我只在我自己的代码中使用这个#define语句,但我一直这么使用,所以很自然地就把它加到你的头文件中了。我要处理非常多的数据运算,所以不能承受你提供的访问函数的开销。但当你用数组取代了单独的double时,我的代码‘轰’了。”
“鲍勃,实际上。”好象事先约好的一样,我们听到了Guru的声音。我们都跳了一下。她用手指推了一下鼻子上的眼镜。”而且,你对编程原则的曲解”她指责道,”是我留便笺在你显示器上的原因。你试图搅乱访问控制的行为打破了类的封装。你在代码中将private改为public的行为违背了One Definition Rule。你必须将你的代码改为使用访问函数。”
“我已经说过了,我不能承受访问函数的开销,亲爱的,”鲍勃辩解道。”我必须-”
“不要说了!” Guru打断了他”访问函数是内联的,不会造成开销。”鲍勃试图反驳,但Guru阻止了他。”那么,为了避免所有的流言[2],我们用测试来证明访问函数的实际代价。在此期间,清理掉那些不象样的代码。然后研读并思考1 Meyers 20[3]。”
鲍勃嘟囔着走向他自己的房间。Guru静静地站着,直到他已经走了,然后领着我回到我自己的房间。
“徒弟,”她轻柔地说道,”请写一段测试代码来计算直接访问的开销,和inline的访问函数以及非inline的访问函数进行对比。要确保非inline的访问函数处理的位于其它的编译单元中。While you are at it, an inline operator [] would be useful.而inline的operator []会有帮助。”
我想了一下。”你是这个意思吗?”我写出:
class Point
{
public:
inline double operator[] (int index) const {
return location[index];
}
};
“很好,徒弟。你记住了正确使用const的法则。但现在,这么写它将不只是一个访问函数,还有个很好的mutator(变异)函数。你能从这个函数得到什么额外的好处?”
我想了一会儿。”可以在下标上进行越界检查。”我修改了一下:
class Point
{
public:
inline double &operator[] (int index) {
assert(index >=0 && index <= 2);
return location[index];
}
};
“嗨,漂亮吧!”这个,我喜欢。”这个越界检查在release版本中没有任何性能开销。”
Guru点点头。”并且,”她又说道,”使用一个优化能力很强的编译器时,能生成和直接访问成员同样高效的代码,而又兼具着封装及其安全性。”
“所以,”我思索着,”实在没有理由存在公有数据成员,是吧?”
Guru在回答前停了一下。 “在绝大多数情况下,没有理由。然而,如果一个类仅仅是一个便利性的数据聚合体,而不是对象模型-也就是说,C风格的struct,只有数据而没有额外行为-那么将所有数据置为公有是合理的 [4]。”
“你可以通过访问函数和mutator函数将私有成员暴露出去-但只在它真的有必要时。多余的Get和Set函数-包括此处的operator []-表明你没有认真考虑封装。当测试表明访问函数真的存在运行期开销时,你可以通过将它们实现为inline来提高速度。当然,所得的提高因编译器而异-所以需要测试。”
“我明白了,老板,”我高兴地回答。她笑了,拢着手静静地走开了。我坐下来写好了测试代码。其结果,至少就我所使用的编译器而言,非常有趣[5] :
Approximate run-time in milliseconds
Implementation
Optimizations off
Optimizations on
1: direct access
1061
251
2: inline accessor function
1673
240
3: out-of-line accessor function
1662
1432
轰。
“我不喜欢这样,”珍妮重复道。”他们说圆屋因为一起意外的内部爆炸而被破坏,所以我们不能去那儿,但那儿的工作还在继续,有着更高许可的人被允许进入并且没有出来过。他们说远距通讯设施被破坏,并限定只供紧急状态下使用,但我能看到的唯一结果是我们不能和近木星空间的其他任何人联系。”
“是吗?”
“我不喜欢这样。”
轰。
我们又有一段时间没说话,最终远处的轰鸣也停止了,但得到的寂静比烦躁的噪音更难受。left.过了一会儿,珍妮皱起眉头,站起来离开了。
[注释]
[1] Note from the authors: this situation actually happened to me (Jim) recently. I urge all readers to install carbon monoxide and smoke detectors and ensure they are well-maintained. I know it's the best investment I've ever made! Contact your local fire prevention office for information and assistance.
[2] “Premature optimization is the root of all evil” — Donald Knuth. Depending whom you believe, he said it in one or more of: “Structured Programming with go to Statements” (Computing Surveys, Vol. 6, No. 4, December, 1974, page 268), Literate Programming, or Computer Programming As an Art (1974).
[3] Scott Meyers, Effective C++, 2nd edition, Item 20: “Avoid data members in the public interface” (Addison-Wesley, 1997).
[4] C++ FAQ Lite, Marshall Cline, ]http://www.parashift.com/c++-faq-lite/classes-and-objects.htm#[7.8].
[5] The source code and full details of this test are available on the CUJ website at hyslop.zip.