我写一个小文,同样在“代码片段”里,题为:《常量成员函数限定的是什么?》。讲的是,在常量成员函数里,可以修改指针成员所指向的数据,因为什么什么的。示例代码如下:
- class Coo2
- {
- public:
- Coo2() : p(new int(0)) {}
- ~Coo2() {delete p;}
- int const * getP() const //常量成员函数
- {
- *p = 1; //编译OK
- return this->p;
- }
- private:
- int* p;
- };
但有郭同学看完后评论: “我的天啊,这种有问题的代码,你竟然认为编译通过,就是完事了么,要是构造函数出现异常,那么是不是就出现内存泄漏问题了。”
嘿嘿,我一下子坏笑起来了。先说个半真半假的故事吧:
那天我家小区来了不知从哪个省流浪过来三个可怜的小姐妹。最小的妹妹表演骑独轮车过酒瓶,老二表演踢八仙桌(人仰卧在地,瘦瘦的双脚有着神奇的力量,一米见方的桌子在空中调整地,各个角度地翻转)。 大姐表演顶盘子,桌子上摆一个球,球上跨一木板,木板前端不断放盘子,大姐站在板上面,一踩,那盘子就上了头顶层层叠叠,很是惊险,我看得真揪心……
以上内容基本是真实的,接着来开始假的内容:
三位姐妹表演完毕后,大姐走到人前唱个诺:“各位叔叔阿姨兄弟姐妹,要是觉得我们仨演得还行,就请帮个忙,赏点小钱吧……” 人群一片叫好,给钱者众。很快走到我面前,我坚决不给钱,围观者或有迷惑不解,我答曰:
“我的天啊,这种表演也敢伸手要钱! 衣服如此之脏兮兮, 人也长得如此之不性感!看看你们的三围!那也敢叫作表演吗!”
--------------------------------------------------------
同样是表演,关注点不一样吧,原来“我”一直盯着人家杂技演员的胸部看! 嘿嘿,郭同学在你看俺这段代码和小文时,肯定是一直盯着俺的敏感位置看了。别这样噢,我会脸红的。。。
不过我还是支持你的,所谓 “世界是相同的,然而眼睛不同,所以看到的世界也就不同”。 (最近我越来越像文学青年了~~)
说点正经的,Coo2 的类设计(如果这样一个小例子也算得上设计的话),我认为不会有你担心的问题(内存泄漏)。 它的构造过程就是一个 new 语句,并且new的是简单对象(int数据)。 它要么成功,要么失败。
再说一遍: 构造过程就一句代码,一句"new内置类型数据”的代码。所以,这个new过程成功了,就代表构造成功了。(除非那时候操作系统蓝屏了或机器掉电了等等不可控原因)。构造成功,接下来交给这个对象的使用者了,只要他最终调用了delete,则对象的析构函数被调用,而我在析构函数~Coo2里,已经做了这个对象理应做的一切:delete p;
(我算是听话的了,很多C++大牛写的课程,为了让读者专注当前要谈的问题,或者只是不想写一堆与当前主题无关的代码,他们根本就不写这些常规的代码)。
另一种情况,就是你说的:“要是构造函数出现异常……” 。如果是你是在说C++异常的话 ,那这个构造函数只能是唯一的那句语句可能出异常。也就是那句“new内置类型数据”的代码抛出异常。嗯。我们都了解C++的new行为在默认情况下,失败时会抛出异常。当然,我们也都了解,如果这个异常没有被调用者代码catch到的话……那么,C行为将要出现,一个在C的世界里就定义好的的一个普通的函数(正好考一下C/C++初学者,这个函数叫什么名字?)将会被调用,然后呢,基本上是这个程序的世界毁灭了……
如果这程序的世界没有over掉,这时,我们要关注你说的那个问题了:“那么是不是就出现内存泄漏问题了” ?
我们已经谈了一个C++标准:new失败后的行为,现在说另一个标准: 当一个对象在构造时,构造函数失败了,会怎样? Delphi的朋友会说,肯定是调用它的析构函数啊。 不过C++是另一套标准:构造失败,则这个对象是不完整的,而C++坚决不允许析构一个“不完整的对象”,于是乎,如果像这样的构造函数:
- struct A
- {
- A()
- {
- p1 = new int[100];
- p2 = new int[100000]; //
- };
- //... 学一下大牛,我不写后面的常规代码了。
- };
如果上面的 (05 行)p1成功地占用了100个整数的内存, 但在(06 行 )new p2 时失败, 则前面p1的内存就泄漏了,因为C++标准不让此时调用析构函数啊。。。。
呵呵。郭同学会有那个评论的第一个可能,应是在指这一种情况吧? 如果是,那那那与我原文的代码何干啊。。。我这里正好就是一个超简单的示例代码,里面就一个分配内存的操作,而且正好我new的还是内置类型的数据,所以,它要是成功了,它就会被在析构函数中释放;它要是失败了,那它就不会占用内存,何来“内存就泄漏了”的担忧呢?
也许吧,你只是希望我写这样的代码:
- class Coo2
- {
- public:
- Coo2()
- try : p (new int(0)) //对付构造初始化列表的异常
- {}
- catch(...)
- {}
- //继续装牛人,不写了...免得又出什么事
- };
这我就要虚构另外一个故事了:
有个胎儿近10月了正要出发,突然听到有个医生肚子外面对他喊:“亲爱的胎儿! 听着,我要向你提一个规定! 如果在出生过程中,因为医院突然断电, 或者因为你个头太大, 或者因为接生你的医生正在偷菜,或者因为 ……等等异常原因,造成你难产挂掉, 请你一定要在挂掉前负责处理好这些异常!包括清理好因为这些异常而造成一切资源占用!! 你愿意遵守这个约定,你就出来吧,否则,你就别出来了!”
需要由“被构造”的对象自行处理构造过程中发生的异常,存在这种需求吗? 存在!在一些非常严格及恶劣的环境下会要求。但通常多数库,要么绕个弯,不在构造函数里分配成员内存(称为二次构造,典型的如MFC或wxWidgets库的Create函数),要么就通通交给外部的世界,就像我们人类的世界,可爱的baby们,你们放心地出来吧……
-----------------------------------------
还是说得复杂了,对于我原文中Coo2那么简单的构造过程,有必要搞一切吗?
就算我的Coo2中有new 1个,两个,甚至更多个对象, 我一定要在我的每一个例子里,都搞一个异常处理吗?
我的天啊!
如果您想与我交流,请点击如下链接成为我的好友:
http://student.csdn.net/invite.php?u=112600&c=f635b3cf130f350c