逻辑上的浅层视图
(译注:相对于实在的对象,视图只不过是我们的眼镜,我们是通过视图看到对象的,学过数据库的同志一定不陌生。)const是一个逻辑上的而不是物理上的概念;它是一个浅层而不是深层的概念;它对视图作出承诺,却未必对对象作出同样的承诺。
Guru推崇(takes on) const.
"嘿,哥们,"Bob边喝咖啡边跟我打了声招呼,"今天过的怎么样啊?""噢,嘿,Bob",我站起身来关掉嗡嗡作响的微波炉,开始吃午饭,"没什么事,你呢?"
"彼此彼此,"他安静的说道,魂不守舍的样子。这不像Bob啊,"只是..."Bob欲言又止,
"到底什么事?"我咀嚼着。
他好像很犹豫,说道:"好吧,哥们,我想你也知道,那个尊贵的女王陛下(译注:指Guru)曾经因为const的事情纠缠我,尽管最近不怎么烦了,可是我觉得也许把const加入新模块将会是一个不错的点子..."
暂时的,我被震惊了。Bob,居然关心const的正确性?我继续由于震惊停下的咀嚼动作,Bob没有注意到...
"...但是不行啊。我完全按照她(译注:指Guru)的指示去做,没有作任何const_cast,但是..."
我觉得我的下巴已经脱臼了,我尽力不让食物从我的嘴里掉出来。Bob,对const的正确性产生了兴趣,并且有意识的回避使用黑客风格(hacky,译注:巧妙但是难懂)的代码?
"...即使是这样值还是发生了变化。我搞不明白了,嘿!"他骤起眉头,"你没事吧?"
"我,嗯,呃,"我装作口吃,但事实上我惊讶于听到Bob以这种方式说话,我还需要一段时间去适应。我指了指口中的食物,"抱歉,呃,太烫了,太烫了!!!"我在嘴边扇着风,"呼..."
Bob用狐疑的眼神看着我。"有时间过来看一眼我的代码吗?"Bob说道。
实在是受不了了,我几乎噎着自己了,但还是勉强点了点头。"啊哈,当然了。"在去Bob办公室的途中,他的咖啡从杯子里溅了出来,我注意到这个细节,说明我还没有失去意识。
Bob调出他的代码,如下(名字是我编的):

看完这段代码以及这个类里面的所有其他函数,我站起身来挠了挠头说:"看上去没有问题,如你所说,没有调用const_cast,你说对象被改变了?"
"是的。这儿,你看。"他用粗短的手指指向了Bob::C++at,在屏幕上留下了一小块油腻。"就在这儿,我设置一个断点(breakpoint)。添加一个Watch。"他启动程序,我们在每个Bob::C++at的调用点处停下。毫无疑问的是,other对象的状态(state)在一段时间内保持不变,接着它就不可思议的发生了变化。
"哼,"我哼道。"好吧,other对象在函数执行的整个过程中都保持不变。至少你没有改变它,那么改变一定发生在这个函数调用的间隙。"
"也就是说改变它的另有其人?"Bob明白了。"但它是const的啊!!"
目的终于达到了,我发现Bob对于const的理解出现了问题。"啊,我明白了。当然了,对你而言它是const。const承诺这个对象的视图从逻辑上来说是const的。除非使用转型操作,你保证不会修改通过这个对象的视图修改它。但是其他人却可以修改它,通过这个对象的其他引用(reference),而这个引用并未承诺const。比如说..."
"什么呢?"
"...是谁调用了这段代码?""不知道。我看一下堆栈轨迹(stack trace)..."我们看着屏幕,接着对视,接着齐声叫道:"Kerry!"没过一会儿,这个恼人的实习生出现在我们的面前。"呃,有事吗?"Kerry充满着期待。
我们笑了笑,在靠近屏幕的地方给Kerry搬了个椅子。Bob调出了Kerry的代码:

"啊哈!"Bob说道。"是你的问题啊。我需要一个const的对象,而你却传了一个non-const的对象给我。"
"啊哈,是你(译注:指Bob)自己的错,"我脱口而出,由于声音太大,我自己也感到有些尴尬,"const跟你想的完全不一样。若一个函数接受const对象的指针或引用作为其参数,你是可以传一个non-const对象进去的。这样做意味着在被调用的函数体中不会修改这个对象的值,至少规则上是这么说的。两段代码访问同一个对象,其中一个承诺不会修改这个对象。当然了,共享状态(shared state)基本就是错误的做法,一段代码对共享对象的改变将会让另一段代码吃惊不小。另外..."
砰!我们一如既往的跳了起来,看到Guru已经站在我们的身后,Wendy站在她的旁边。她把手中那本刚刚砰然合上的大部头递给了Wendy。
"小伙子们,"她说道。"Kerry没有做错。他甚至保证了传递给Bob的那个Other对象的生命周期要比Bob类中出现的那个还要长一些。"
"而且,"Guru接着说道,"即使加上了const,对象的状态仍然可以改变,因为const是逻辑上的-"
"瞧,各位,我还没那么幼稚,"Bob幽怨的说。"我知道可变成员变量(mutable members),而const只是逻辑上的概念。"
"并且是一个浅层的概念,"Guru打断了Bob."浅层?""怎么个浅层法?" Kerry 插话道。
"考虑这个类似的情况,"Guru拿起马克笔写道:
"这个变量在名字字符串和电话号码之间建立了一个映射关系。map所包含元素的类型是什么,Wendy?"
"是pair<const string, string>。"
"没错。sets和maps的键值(keys)是否应该是可变的一直以来都是争议不断。最终的决定是它们是不能被改变的。但是,除此之外,如果使用对象作为容器的键值,不可以使用可变的或者是外部的状态来决定它们是否相等,考虑下面这段代码:"她写道:
![]()
"这里容器元素的类型是pair<const File, string>。会发生什么呢?"她问道,"如果File封装了一个文件以及它的比较元算符,该运算符用来比较磁盘上文件的内容。"
"啊,"我说道,"我明白了。如果磁盘上的文件内容发生变化,File对象没有改变,仍旧是const,但是和先前的比较不同了。这将会破坏map的排列顺序。"
"的确如此。作为set和map键值使用的对象之间的比较不可以访问可变状态。"
"那是够烂的了,"Bob插话道。"谁会写出那样的代码呢?"
Kerry红着脸缓缓的举起了手。Bob很不屑。"呃,"Kerry站了起来。"我写过一个QueryView类。QueryView的对象可以是const,这些对象共享一个可变的Query对象,这个Query对象中包含了可更新的查询结果。"
"没有关系,"Wendy说道。"看看这个。"她写道:

"瞧,const是浅层的,"她边说边把手中的马克笔放下。"指针是const,我也不会修改它...但是我可以通过它去修改其他东西。智能指针(smart pointer)的operator*和operator->不会改变指针本身,所以它们应该是const,但是它们的返回值未必是const。"
"这样说来const只是对对象的视图有效,"我总结道。"而且它是逻辑上的,因为其可变的或者外部的状态仍然可以修改它。同时它也是浅层的,因为我可以通过一个const对象修改其他non-const对象,而不需要任何转型。对吗?"
"的确如此。"Guru说道。
"嗯,我也是这么认为的。"Bob低声说道。
"我也同意。" Kerry说道。
"是的。"Wendy点了点头.
"我得说,"我讥讽道。"我们在逻辑上对const的理解还是很浅层的。"
几个人叹了口气。Guru转身离去,在很远的距离向我的方向皱了皱眉头,厌恶的摇了摇头。慢慢的从走廊走了出去。
偷笑。
致谢
感谢Hubert Matthews提供了这个话题。












逻辑上的浅层视图


springrider 探花 | Blog | 07/17/2007
这个系列确实非常好,我读过一个chm版本的,加油!
易晓斓 状元 | 07/18/2007
正如前面读者评论的,是一个有意思的系列。很好的选材。
Leo翻起来也很用心,多谢!
只有一点意见,shallow在这里,我认为应该翻成“浅层”,而不是“浅薄”。
“浅薄”,是贬语。而这里的语义环境是分析技术上的问题,是指向一个对象的指针或应用所代表的“浅层”,和被指的对象所代表的“深层”。
所以,这里应该使用技术感觉强一些的中性词,“浅层”。Leo以为如何?
leomayleomay 进士 | Blog | 07/18/2007
谢谢易晓斓提出的建议,在翻译这个系列的时候,有很多词都拿捏不准,shallow就是其中之一,我已经将其修改过来,希望您能继续关注这个频道,提出您宝贵的意见,谢谢。
FindWorld 举人 | Blog | 10/03/2007
翻得挺不错的,我看过一本这样对话的pdf,不知是不是c++ report,没有注意。
Dr Dobb's Journal 的原文在哪里获得,我希望自己能有所贡献~~