[声明]:本英文资料源自于Herb Sutter 创建的“Conversation”栏目,“C++ 翻译小组”的翻译作品供学习交流与参考用途,不得用于任何商业用途。未经Herb Sutter、Jim Hyslop同意,不得转载;对于违反以上条款,翻译小组对此不负任何责任;特此声明。
文章来源:http://www.gotw.ca
版权归属:Herb Sutter and Jim Hyslop
译 者:sebastian(CSDN)
对话#24:好的,坏的,不赞成的
关于我第一次程序设计工作的可笑事(这是很多年以后,我在与我的新婚妻子珍妮喝早茶时谈起的。)是鲍勃如何死皮赖脸地不想离职,尽管他根本无法胜任。我记得一个很特别的天气晴朗春天的早晨....
当时我正在努力地完成一段新代码。这时鲍勃逛到了我的工作隔间,依在隔间的墙上,喝了一口咖啡。--不,我不知道他手上为什么总是有一杯咖啡,鲍勃站在那里微笑的说:"嗨!菜鸟。”
我漫不经心的说:“嗨,鲍勃,什么事?”
“这段代码你上周检查过了吗?”他耸耸肩,“好,它破坏了整体结构。编译器说你定义了Output函数两次,你不能这样做。”
“什么?”我诧异的说:“但是,我肯定我已经生成了……”我快速的把我一周前做的文件调入了编辑器:
//--- file trans.cpp -----------------------------
//
static void Output( ostream& o ) { /*...*/ }
// ...
void ProcessTransaction( /*...*/ )
{
// ...
Output( cout );
// ...
}
“这些,不是很好吗,”我说道,坐了回去,心里很满意,看来鲍勃完全是无中生有。
鲍勃笑了笑。“继续看下去。”他催促道。
我皱了皱眉,用grep扫描了源代码。期望发现Output函数在同一模块中是否
还出现在其他什么地方。另人惊讶的是,我在另3个文件里发现了它。
//--- file output.h ------------------------------
void Output( std::ostream& );
//--- file output.cpp ----------------------------
#include "output.h"
// ...
void Output( ostream& out ) { /*...*/ }
//--- file dbinit.cpp ----------------------------
//
#include "output.h"
// ...
void InitDatabase()
{
// ...
Output( clog );
// ...
}
我沉思了一下。“但是,这是同一个Output函数吗?”我问道。
鲍勃不耐烦的用拿着咖啡杯的手比划了一下,"你刚才没听我说吗?我刚才告诉你了你写的代码。无法正常生成。"
“生成什么?”
“就在我们的嵌入式目标平台上,至少我看到生成器抱怨无法生成。修正它,弄完后告诉我一起,我可以接下去干。”鲍勃呷了一口咖啡,叫了几声,然后离开了。
好了,我老实承认:一开始我并不相信鲍勃的话。
我试着用我们一直用的编译器,重新生成,一切正常。我受到鼓舞,更换了Shell,用我们用于嵌入式目标的编译器重成生成,结果无法生成。我呆了下来,喃喃自语:我用的第一个编译器允许两个Output函数共存,但另外一个编译器却抱怨说存在重复的定义,尽管前后两个是同一段代码。
我打算放弃了,并去向温迪求教时,我听到了我身后传来了一阵飒飒的翻书声,这时一声合上书的声音从我身后传来:“需要帮助吗?"Guru的声音从我后面传来了:“你好像遇到了什么难题了,需要我帮忙吗?”
我转过来看到她在微笑,“当然,”我很高兴有人来帮我,“这是我写的代码,这里是编译器反映的错误,我到底错在哪里了?”
她快速的扫视了一遍代码和编译结果。“哦,”她说,“这里有两个问题,但是我们以前已经讨论研究过了这个问题,你认为发生了什么事?你尽所能解释一下发生了什么。”
我接受了挑战:“好的,对...一个是在trans.cpp中,我希望在ProcessTans中调用Output是那个静态版本的Output。”
“还有呢?”她催促道。
“另一方面,在dbinit.cpp中,我希望在InitDatabase中对Output的调用将会是外部定义的Output,存在于output.cpp。”
“但是...?”她又催促道。
“值得注意的是,我们的主要编译器看来同意我的做法,否则的话,我在代码上交前就应该发现问题。但另一个编译器却不允许这样做。”
“做的很好,”Guru平静的说,“现在沉思一下你所看到的东西,你将发现这里的确有两个很明显的问题,第一个问题是嵌入式编译器出错了,很明显你的的代码是合法的,而我们主要使用的编译器确实对这段代码进行了正确的处理。一个函数如果是被声明为static的话,仅仅是做内部连接,也就是说,根据概念3:5第三段的标准来的,但是其他的output是进行外部连接的,依据概念3:5第九段,它和trans.cpp内的静态版本的Output不是同一个函数,这就是说为什么嵌入式编译器出错,无法正确辨认出两个Output,这是两个截然不同的函数,而不是一个。”
“原来如此,”我放心的说,“酷,这下子我懂了。”
“你肯定吗?第二个问题是,”Guru继续说,“尽管你代码是合法的,但是它并不遵守我们的编码标准。”
“什么?我使用了正确的对齐和缩进啊。”
Guru退了一步,紧紧闭住眼睛,并挥手示意我安静。“不,不,不,不。没有哪种编码标准值得你浪费时间这样定义东西,包括我们。除了,当然,嵌入表是有害的,不管是用哪种形式的使用空格的缩进。不,你可能看的是鲍勃在他以前工作时带来的标准,但这些东西现在并不适用。”
“还有,”她继续道,“问题是你对static的使用,你这种static的使用方法是老式的、陈旧的、不提倡使用的,而且大多数都被认为是有缺陷的。”。她打开了她的书,现在我知道那是Stroustrup的The C++ Programming Language [1],找出一处临近书尾的地方,引述道:“用static来表示‘本地于编译单位’在C++中是不被提倡的...”她透过了眼镜看了看我,继续引述,“用无名的名字空间来代替。”
我灵机一动,“哦,我知道该怎么做了”,我改正了代码:
//--- file trans.cpp -----------------------------
//
namespace
{
void Output( ostream& o ) { /*...*/ }
}
// ...
void ProcessTransaction( /*...*/ )
{
// ...
Output( cout );
// ...
}
“当然,我的徒弟,我现在确实相信我们过时的嵌入式编译器可以正确的处理你修订过的代码。”声音渐渐的消失,我转过身去发现Guru已经不见了,就像她来的时候。
我笑了笑,活动了一下指关节,成功的重写了代码,并通过了所有的编译器编译,和单元测试,并呈交了代码。我微笑的打开了email,给鲍勃发了一封简短而又含糊其词的通知。
[参考文献]
[1] Bjarne Stroustrup. The C++ Programming Language, 3rd Edition (Addison-Wesley, 1997).
[关于作者]
Herb Sutter
是个独立顾问,也是ISO/ANSI C++标准委员会的秘书。你可通过hsutter@acm.org.联系他
Jim Hyslop
Leitch Technology International Inc.资深的软件设计师,你可通过jim.hyslop@leitch.com联系他