对话#28:Contracts, Promises, and Mere Semantics
和大多数日子一样,我开始了那天的工作-在我的方形房间内,端着新鲜的咖啡,在开始写代码前,正收着早上的email。很奇特,它这天,Guru没有突然出现在我身后。实际上,我无意中听到它发生在另外一个人身上。
我正坐在书桌前安静地工作着,而且听到附近的同事们击键的时间,这是声音传来。
“时间到了,”Guru说,“谈些事吧。”
我习惯性地跳了起来。我四处寻视了一下,但Guru并没站在我身后。我接着听到隔壁房间传来椅子的吱吱声-是Kerry,我们组聪明而又令人讨厌的新人,从他椅子上跳起来。她正和他在一起。我怀疑他能持续多久;Guru以最开头几个月内吓跑绝大多数的新人而著称,根本等不到转正。Kerry已经表现为早期的崩溃症状。我估计他将只能再坚持几个星期了。
我怀着稍微的内疚偷听着:
“鲍伯告诉我,我的新代码中有一个问题……”我听到Kerry开始反抗。
“是吗,我的孩子?”我能回想出她的眉毛翘起来,向我笑着的样子。
“嗯……是的。是关于编码规范的事,我将自己搞定它……”
我听到了书合上的声音。Guru肯定已经合上了她带的“砖头”。我懒得猜她今天带的是哪本。“告诉我,年轻的,”我听到Guru对Kerry说,“我们的编码规范说什么了?说吧。”
“嗯……他们就在这里。”在Kerry击键的同时,我也悄声地调出同一文件并找到他正背诵的位置。这是其中的一部分:
当申明参数类型时,最好遵循传统的C++接口惯用法:
l 如果实参需要被更改,传指针。
l 如果实参不被更改,传值。
“我没这么做,所以鲍伯指出它。就是这么多。这是对的。这是对的;我将修改;真的,”Kerry说完了,听起来很紧张。
有一个停顿,我在猜测Guru是在微笑或皱眉。最后,她说:“照他的话做……就现在。更新代码,但别chek in。而是把更新过的代码给鲍伯,让他查看,并让他自己使用,然后等待。”
“等候?……为什么?”Kerry问。
这次我确定Guru在微笑。“只需等待,徒弟,”她说道,然后我听到了纸的沙沙声和后退的脚步声。
表演结束,我想,于是回去工作。但是最精彩的部分总要上演的……
几个小时之后,我正狂写代码时,听到Kerry被叫到鲍伯的办公书桌,虽然稍微有些远,但还没远到让我听不见鲍伯狂训Kerry的声音。
又过了一会儿,鲍伯似乎训够了,Kerry被放了回来,孤独地 (我想)回到他的房间。当他坐下时,椅子吱吱地响了一下,然后又变得寂静……但没维持多久。
大约五分钟之后,又次传来Guru的声音,Kerry的椅子又吱吱响了一下(或是Kerry发的?他已经习惯在Guru出现时发出吱吱声),又一次的对话:
“关于C语言为什么是烫手的,”Guru突然说道,“而猪是否有翅膀。”我在笑后面一句话,知道她可能在说鲍伯。
(吱吱声)“我……我跟鲍伯说了……”
“的确,你这么做了,孩子,越过整个建筑我都能听到,”传来Guru柔和的声音。又传来一声书本很快合上的声音。“而他说了什么?”
“我的代码一定是错的;它让他代码不能工作了,”Kerry结结巴巴的。“但是我不知道我做错了什么。我就是按他所说的做的!我也就是按编码规范说的做的!”
“啊,”-我眼前浮现出Guru锐利地微笑-“但它们是同一回事。因为鲍伯是制订这部分编码规范的人之一。”
“……他……他是?”
“哦,是的。他确实是,徒弟。这也就是编码规范发生错误的原因。”
“错误?”
“那对C++惯用法而言是错误的,”她继续说道。
“虽然,它对于没有'引用'的C语言是正确的。”
“但是……如果是错的,他们为什么不更新?”
“我们的新经理,Pete Williams,过于信任鲍伯而看不清实际需要,”她叹息道。“他的前任也没有这么做。于是,编码规范并不正确。C++惯用法更象这个……”我听到了书写笔在白板上的吱吱声,这是我后来在白板上看到的:
当申明参数类型时,最好遵循传统的C++接口惯用法,除非它不合适:
l 如果实参需要被改变,传非const的指针或引用。
l 如果实参不被改变,传const的引用,或在拷贝开销很小时传值。
“这样的陈述比较接近事实,”Guru总结了一下,“但是,徒弟,你必须注意'最好'和'除非'这两个词。这只是惯用法。不是教条。它不是没有例外的。”
“嗯……是的。没问题。鲍伯说,它适用于他试过的每件事物,除了……”
“auto_ptr对象,”Guru点点头,替他说了出来,我听到她继续在白板上写着。
Kerry发出了惊讶的声音。“你怎么知道的!鲍伯告诉你的?你看见的?”
“我就是知道。我不需要看见。”她继续在白板上的写着,而这里是我几分钟之后看见的:
template<typename T>
void Mutate( T* byPointer );
template<typename T>
void Mutate( T& byReference );
template<typename T>
void LeaveAlone( const T* byPointerToConst );
template<typename T>
void LeaveAlone( const T& byReferenceToConst );
template<typename T>
void LeaveAlone( T byValue );
“想一下这个例子,然后回答:对于哪种类型的T,函数的名字是名副其实的?徒弟!”她说道。
我不知道她说的是我,直到我听到一种不同的啪嗒声,一块橡皮从我头上反弹开去。然后我很快反应过来,揉了揉头,加入他们。
“嗯?什么?” 我嘟囔了一句。“我正忙着工作。” “实际上你正专心于竖着耳朵。”Guru眯起眼睛一会儿,假装烦恼, 然后我看见她闪烁的目光。放下书写笔后,她又打开她的厚书,将头发挂于耳后,微笑着走开了。“我的徒弟将会解释的,我的孩子……” 她的分别语从肩头飘来,就在她转弯消失的那瞬间。
我摇头咧嘴而笑。什么行为!但Kerry古怪地看着我,我怀疑也许他还没开始把我和Guru同归在“神奇人士”之列。我发现我不会介意的,如果他这么做了的话;这给了我一个有趣的启示。
“年轻人,”我微笑着说道,一认识到我刚说了什么,就笑得更欢了,“当你传递一个auto_ptr是,会发生什么?”
“嗯……噢!”很清楚,Kerry已经明白了。“传递了所有权!她说的是这个意思吗?”
“是的,”我同意。“你已经明白了。而同样事还发生在所有会转移所有权类-顺便提一句,其典型特征是在拷贝构造函数的形参上使用非const的引用。这就是你要的答案:传值通常意味着你不会碰源对象,除了auto_ptr,对它含义正相反-并且更差,因为不但修改了源 auto_ptr,还将它置为了NULL。而传指针常意味着准备修改源对象,除了auto_ptr,对它含义正相反-你正在避免修改它。对于auto_ptr,传统的惯用法正干着坏事。而这正是基于通常的惯用法的代码,例如容器对它们所包容的对象作的假设,不能和auto_ptr协同工作的原因。但不只是对于auto_ptr;对所有在拷贝时进行所有权传递的类都是这样。总要特别留心在拷贝构造函数中的非const引用参数;那就是你通常会遗漏的致命之处。”
“现在,”我一边走一边让我的声音飘向身后,“我必须回去继续我的沉思了。”我到达走道末端,转了个弯,刚从他视野中消失,就再也忍不住得发出了吃吃地笑声。
[参考文献]
[1] H. Sutter and J. Hyslop. “Conversations: A Midsummer Night's Madness,” C/C++ Users Journal C++ Experts Forum, August 2002.