关于对象生命历程的会话
作者:Jim Hyslop 翻译:宋科
出处:C/C++ User Journal 2002 Dec
(译者注:我从网上看到这篇文章的原文,非常喜欢作者的写作风格,于是就利用业余时间将它翻译出来,并贴到网上,希望大家可以从中受益,因为我没有和作者或者这篇文章的版权所有者联系以取得这篇文章的版权,所以这篇中译文的版权不应归我所有,而且我未从中获得任何利益!而且,我对原作者及其版权所有者的敬仰有如涛涛江水,所以我绝没有侵犯原作者的任何意图。当然,要说“利益”还是有的,就是我加深了如下C++的知识:只能被绑定到一个常量的、非易失的引用上,左值却没有这样的限制;函数返回的引用是左值。而且不管怎么说,如果一个对象的构造函数中引用了的临时对象,而对象在该构造函数构造结束时就消逝了的话,那么该对象就成了一个“野引用”了。如果我理解得有错的话,请一定告诉我一声,谢谢,kesongemini@vip.163.com。还有
Herb Sutter 先生的《more C++ Exceptional》中文版已经由华中科技大学出版社出版,小弟窃喜着买了一本,沈阳三好街的大松科技书店进新书的速度快得惊人,都是8折,不知道还有没有更便宜的地方:)
假日前不久的那几天,我就那么一次没有感觉到最后期限(deadline )的压力――我正在做的这个项目都已经按计划完成了。
我选择了最喜欢的消遣方式,撇掉了source repository(译者:源码智囊团?不懂,是一款分析源码的软件吗?)。我经常在研读别人代码时,学习一些他们的技巧(对我来说是新的),以及学习如何避免他们代码中的错误。
class T
{
public:
T & ref() { return *this; }
};
void f( T & );
int main()
{
f( T().ref() );
}开始时,我没有理解ref()函数的重点,因此我删除了对它的调用――我认为下边这样的代码应该可以正常运行:
int main()
{
f( T() );
}
然而,当我编译时,编译器提示有个错误:将非常量引用绑定到一个临时对象。我轻拍着前额――当然了,这种绑定是不允许的。回想起第一次遇到这类错误的时候the
Guru(译者:下文按领袖译)给我的解释。
她说道:“禁止这种绑定的一个原因是为了防止狡猾的bug,我的孩子,看看下面的情况”
class U
{
// ... 任何代码 ...
};
void takesAndModifiesU( U & u )
{
// 执行操作以修改u的状态
}
class V
{
public:
operator U();
};
void g()
{
V v;
// ...
takesAndModifiesU( v );
// ...
}
“如果允许那种绑定,编译器将调用转换操作符operator(),创建一个临时的U类的对象。这个匿名的临时对象将被传递到takesAndModifiesU,被修改,接着在函数调用完成之后被丢弃。原来的对象v,不会被改变――这会让这段代码的作者困惑不解的。”
然而,我真的困惑了。我不能理解原来的语句f(T().ref());为何能能够编译――那不也是将一个非常量引用绑定到一个临时对象上了么?
“我的孩子,你必须学会多思考一步”,领袖(The Guru)的声音从我耳边传来,而不是记忆中,让我着实吃了一惊,“考虑一下左值和右值。神圣的标准(ISO
C++ 吗?)告诉我们可以用形式为T()的显示的类型转化创建一个右值。一个右值只能被一个常量的、非易失(non-volatile)的引用绑定,但是一个左值没有这样的限制。而且,函数返回一个引用的话,那么返回值就是左值。因此,编译器能够将绑定ref()绑定到一个非常量引用。”
“这样呀,只有我调用某一个返回值是引用的函数,就可以了吧,”我回答道,“嗨――赋值操作符返回一个引用,因此我可以这样写吧f( T() = T()
);不错吧!”我热情洋溢。“我能想到这个技巧的许多用处哟。”
“小心,我的孩子。这种不常见的技巧可能是很危险的,不可以轻易使用的。事实上,我至少可以想到一种情况:是关于对象生命的,这样的话可能会导致未定义的行为。”
“你是指...”我提示了一句。
“吃完午饭之后再说吧,”领袖平静地答道。我看见远处的几个同事正在准备去吃午饭。我一把抓起外套,加入了他们,接着,我们向当地的一家饭馆走去。
不知何故,午餐时我们一直控制着不去谈论关于购物的话题。讨论多集中于我们最感情趣的假日电影,比较《三十四号街的神谕》重拍前后的不同,还有Alistair
Sim 和 Patrick Stewart 哪一个更加吝啬。(我选了Stewart。)鲍勃对我此举感到奇怪,然而――我认为他最喜欢的角色会是Grinch,但是他却非常喜欢《美丽人生》。
当我吃完午饭回去时,我心满意足。我坐在桌子前,开始集中精神考虑领袖一会儿会说些什么。最后,我努力地保持着清醒,并写下了如下代码:
class U
{
T& t_;
public:
U( T & t ) : t_( t ) { }
};
{ // ... 一些域块 ...
U u( (T() = T()) );
// ...
}当对象u完成构造时,临时对象的生命就终结了,对象u将成为一个野引用(译者:因为有人将被释放后但是没有标示为NULL的指针称作“野指针”)。
就好像T的对象从来都没有存在一样,我沉思着疑惑着。
“好吧,乔治,”我听到领袖的声音,“你得到你想要的了。你永远不会出生了。”
“啊?谁是乔治?”我看着领袖。
“就是你呀,”领袖回答道。我一看到她我就知道我我正在做梦――在梦中,你自己知道自己是在做梦,但是你确不得不深入其中。“你就是乔治·贝利。你创建的对象从来都没有存在过。我的工作就是在你处理未定义行为时,给你看看会发生什么事情。”
我看见远处的一条标语:“欢迎你,鲍勃维尔”
“鲍勃维尔?”我问道,同时担心着答案。
“这座城市正在被你的仇恨所融化,鲍勃,”领袖说。“他也是这所银行的主人,这个软件部门的领导。来,我们沿着大街走走吧。”(译者:非常抱歉,这段话我不会翻译,仅按字面意思)
当我们正沿街走着时,我看到了无法形容的恐惧――单片集成代码块,变量名i和j,还有商业广告“这里是空指针解除引用”。在街道的一个拐角,我看到一些程序员不知羞耻的拷贝、粘贴着代码。
我看凯利正开着一辆出租车。我示意他停下了。
“带我离开这里,凯利,”我边爬进车里边喊道。“带我回办公室去,在那里我可以写出理智的代码。”
“凯利?我叫奥尼尔。我不知道什么代码;我只是个的哥。”我们驱车时碰到了温迪,她身穿一身刑警制服。(译者:估计是code inspection 的主力吧:)凯利向她挥手,想引起她注意。我知道这只是场梦,我不会就当没有看见他在干什么,忽略他。
我们在破旧的办公室大楼前停了下来。我大跳出来并钻进了里边。我发现了一块白板,那些恶劣的代码就写在那上边,我把它重写了:
{ // ... 一些块域 ...
T tmp;
U u( tmp );
}
我一把最后一个标记写完时,突然醒了过来。 “我的天!”我自言自语着,我跑到了餐厅,弄来了一杯可以浓浓的咖啡。
注释 [1] ISO/IEC 14882:1998(E), "国际标准,编程语言――C++",条款 5.2.3, 8.5.3, 3.10。
关于作者 Jim Hyslop 是Leitch Technology International 公司的一名高级软件设计师,可以通过电子邮件 jhyslop@ieee.org
和他取得联系。