关于对象生命历程的会话
作者: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将成为