在黑暗中举起探索的火炬的网志
在喧闹、混杂的生活中
你应该与你的心灵和平相处
尽管这世上有很多假冒和欺骗
有很多单调乏味的工作
和众多破灭的梦幻
它仍然是一个美好的世界
记住:你应该努力去追求幸福。
是的,记住:你应该努力去追求幸福。
每个早晨灿烂的太阳升起的时候,
每个人都应
-
2004-04-05
[转载] Solmyr 的小品文系列之七:异常 - [C/C++开发专辑]
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
Solmyr 的小品文系列之七:异常 Elminster -------------------------------------------------------------------------------- 大雨。 乌云象铅块一样低低的压了下来,豆大的雨滴打的玻璃窗啪啪作响,难得一见的异常天气正在竭力表现它令人讨厌的一面。不过这一切似乎并没有影响到 Solmyr,他仍然以他习惯的舒适姿势半躺在宽大的椅子里,手里还托着一杯热腾腾的果汁,在他背后,zero 在键盘上敲打着什么。 “唉,Solmyr ,标准库中的 stack 怎么会是这个样子?设计糟透了。”zero 停止了工作,转过身来面对 Solmyr ,看起来有些困惑。 “胡乱批评被纳入神圣标准的成员是会遭天遣的。”Solmyr 低着头,以一种算命先生似的语调答道。 不知道上天是否打算加强 Solmyr 的说服力,恰在此时天空划过一道闪电,蓝白色的电光挣扎着努力向地面扑来,紧接着就是“喀喇”一声巨响 ——— 这个雷很近。 一秒钟前还在想“这未免也太扯了”的 zero 表情一下子变得很古怪,良久才恢复正常。他标出了两行代码接着说到:“好、好吧,Solmyr,那请你解释一下为什么 stack 的界面是这个样子。” std::stack<int> si; …… int i = si.top(); si.pop(); “只要让 pop() 返回栈顶元素就可以把上面两行合成一行,而且更加直观,为什么要搞成现在这样?” 目睹了 zero 表情变化的 Solmyr 强忍住放声大笑的冲动 ——— 老天知道他忍的有多辛苦 ——— 缓缓的把杯子放到桌上,转过身来开始讲解这个问题: “原因在于异常。” “异常?” “对,很多代码在没有异常的时候工作的挺好,但是一旦出现异常就变得不可收拾,就像一间茅草屋,平时看起来没什么问题,一遇到今天这种天气 …… ”,Solmyr 指了指窗外,“ …… 立刻就会垮掉。考虑一下如果 pop() 返回栈顶元素需要怎样实现,假设栈内部用数组实现,且不考虑栈是否为空的问题。” “很简单啊。”,zero 打开了编辑器,写下: template <typename T> T stack<T>::pop() { ... ... return data[top--]; // 假设数据存储于数组 data 中,top 代表栈顶位置 } Solmyr 摇摇头:“这就是茅草屋。要知道 stack 是个模板类,它存放的元素 T 可能是用户定义的类。我来问你,如果类型 T 的拷贝构造函数抛出异常,会出现什么情况?” “嗯 …… 按值返回,返回值是个临时对象,该临时对象以 data[top] 拷贝构造 …… 嗯,这样一来函数返回时可能抛出异常,客户此时无法取得该元素。” “还有呢?” “还有?” “提示,你的 top 怎么了?” “ …… 哎呀!糟了!top 此时已经减一,栈顶元素就此丢失了!这样的话 …… 必须实现一个函数允许客户修改 ……”,zero 说不下去了。他想了一会,摇摇头承认失败:“不行,这里拷贝构造发生在函数返回之后,无论如何无法避免这种情况。只能在文档里写明:要求 T 的拷贝构造函数不抛出异常。” zero 停了一停,小心翼翼的问 Solmyr :“这个不算过分的要求吧?” Solmyr 的回答异常简短:“new” “哦对,new 在分配内存失败时会抛出 std::bad_alloc …… 算我没说。Solmyr ,我明白了,为了处理异常的情况,调整栈顶位置必须在所有数据拷贝完成之后,所以按值返回是不可接受的。” “正确。所以对于一个设计目标是最大限度可复用性的标准库成员而言,这是不可接受的。” Solmyr 顿了顿,继续说到:“而且异常带来的影响远不止此。我刚才说‘假设栈内部用数组实现’,但如果你充分考虑抛出异常的各种可能性,你就会发现用数组实现是糟糕的主意。” “ …… …… …… …… …… 这是为什么?在没有传值返回的情况下,我们总可以捕捉到发生的异常并加以处理啊?”,zero 谨慎的发问。 Solmyr 赞许的看着 zero 。“发问之前先自行思考,习惯不错。”,Solmyr 心想,但是脸上一点也没表现出来:“没错,但捕捉到异常不代表你总能正确的处理它。考虑一下 stack 的赋值运算符,如果我们用数组来实现,那么在拷贝数据的时候肯定会有类似这样的一个循环:” // 各变量的意义与上面相同 template <typename T> stack<T>& stack<T>::operator=(const stack<T>& rhs) { ... ... for(int i=0; i<rhs.top; i++) data[i] = rhs.data[i]; ... ... } “现在考虑类型 T 的赋值运算符可能抛出异常,该怎样修改上面的代码。” Solmyr 停了下来,再度捧起了杯子。 “用 try 把 …… 哦 …… …… …… …… …… ……”,zero 似乎发现了问题所在,沉默良久,才接着说到:“这个循环可能在运行到一半的时候抛出异常,这样会导致一部分数据已经成功赋值,另一部分却还是老的。除非我们用 catch(...) 捕捉所有异常,忽略之并继续赋值。” “但是这样 ……”,Solmyr 有意识的引导 zero 继续深入思考。 “…… 但是这样,赋值运算符抛出的异常就被我们‘吃掉了’,异常总是代表着某些不该发生的事情发生了,所以应该让客户接收到这个异常才对。” zero 皱着眉头,一字一顿,显得相当辛苦。
http://junglesong.yourblog.org/logs/137068.html
随机文章:
[转贴] 你会用sizeof吗?(vc篇) kernelhao(原作) 2004-04-09[转载] Solmyr 的小品文系列之六:成对出现 2004-04-05ZT:欣赏优美的代码(2) 作者:axing 2004-02-07ZT:欣赏优美的代码(2) 作者:axing 2004-02-07学习c++的50条忠告(转载) 2004-02-07
收藏到:Del.icio.us





