C++,算法,设计模式《转》

画外音:公司有人员变动是家常便饭。而新人好像总是先被派到我们小组,感觉我们这里越来越有新兵训练营的味道了。算算Young来这边也有段日子了,基本的东西也学得差不多了。本以为可以过上清静一些的日子了,可谁知道这两天又冒出来一只“困猫”。这家伙和Young不一样,有一些项目经验和底子,不过好像基本功不大扎实,于是乎,就有了下面的故事……

所谓万事开头难。这两天办公室里多了一只体型不算小的“困猫”,似乎大家都有些不习惯。不过奇怪的是,当初Young刚来的时候,好像没有人不习惯吧。这大概也就是美女与“困猫”的差别吧。

不过呢,“困猫”这家伙脾气不错,给大家的感觉还是蛮好相处的,只是,要将“困猫”这么清纯可爱的名字与眼前这个人高马大的男人相关联起来,还是需要一定的时间来适应吧。

古人云:“说曹操,曹操到”。把这句话里的“曹操”改成“困猫”,也同样合适。而且比“曹操”更神的事,这家伙不用你说的,只要一想到,就能在你眼前晃过。武侠小说中的高手都是剑随心到,念及之处,乃剑到之处。于是乎,把剑换成“困猫”,高手仍旧是高手,只是术业有专攻,正所谓……

“嗨,困猫,过来帮个忙吧!”一个熟悉的声音打断了我正在肆意发挥的思路,估计也就只有Young的声音有这个能力吧。

“哦,好吧。”典型的“困猫”式的回答,加上典型的“困猫”式的清纯语气。

于是,那个庞然大物又一步一步地开始往Young所在的位置移动。我这么说给人的感觉应该是一种极其缓慢的移动速度吧。哦,好吧,那就算我用词不当,这家伙的移动速度还算正常的,不是特别慢的那种。想着想着,发现手上的一杯茶喝完了。好,那就借着去泡杯茶的功夫,顺路去看看Young那边到底有什么事情,而且,还能顺便观察一下“困猫”的表现。说实话,除了技术,观察各种各样的人也是我的一大爱好,像“困猫”这种极有特点的人,还是很有观察价值的。

“困猫,你以前有没有用过C++里的exception啊?”原来Young要问这方面的问题,说起来,好像的确没有给这个家伙讲过这方面的东西,而且也没让她去好好看一下这方面的东西。

“用过一点吧,好像没什么特别的呀,感觉那个throw就像return,而catch就像函数,然后根据参数不同,重载了一下而已”“困猫”依旧维持那种清纯的语气,只是这个回答让我嗅到了会出现问题的征兆,嘿嘿。

“是吗?”Young似乎又要开始发问了,“那我有个问题想请教一下。”

“哦,好吧。”“困猫”这家伙又开始了,“你说吧。”

“嗯,问题是这样的。假设我在一个函数里将一个global的对象给throw了出来,然后在用catch by reference 的方式来捕获这个异常,那么我在catch里改变这个对象的成员变量,有没有作用呢?”

“应该有的啊。因为那个对象是global的,所以即使退出了那个函数,这个对象依旧是有效的,所以,通过对它的引用来访问它,应该是没有问题的”

“那么,如果我是把一个local的对象给throw出来了呢,仍旧使用catch by reference的方式,在catch里面修改这个对象的成员变量,那么有没有作用呢?”Young开始了其惯用的步步逼近策略,不知道“困猫”那家伙是否能抵得住,嘿嘿。

“这个…… 应该不行的吧。local对象的作用域就在那个函数里啊,退出了这个函数的时候,那个对象应该就被析构掉了吧,然后再用对一个引用来访问它,应该是无效的,或者说,会有问题的。嗯,没记错的话,应该是这样的吧。”“困猫”的回答貌似有模有样、有根有据,不过,他明显有点缺少信心了,大概他自己也觉得可能有问题吧。

“噢?真的是这样吗?”Young以一种怀疑的语气,哦,不是,是一种肯定的语气,肯定“困猫”的答案是错的,“那你来看看下面这个程序吧。”

#include

using namespace std;

struct Err{
int errNo;
Err(int val = 0)
{
errNo = val;
}
};

void f()
{
Err lErr(1);
throw lErr;
}

int main()
{
try{
f();
}
catch(Err& err){
err.errNo++;
cout << err.errNo << endl;
}
return 0;
}

“你说这个运行的结果是什么呢?”Young的语气有点挑衅的味道,看来的确不能给这家伙抓到把柄,否则看看“困猫”现在那个无辜的表情,就知道有多惨了。

“这个…… 感觉catch里面去访问了一个已经无效的对象,结果好像不可预期的吧。要看编译器怎么处理了吧。这里的结果么…… 大概、也许、可能是1吧。”“困猫”自己已经开始迷惑了,不过还真能掰,扯到编译器上去了。

“不对哦,给你看看运行结果吧。”Young抓到了“困猫”的把柄,一付得意的样子,感觉就像高手要开始给菜鸟上课了。而再看看“困猫”,虽然从我的角度只能看到那个巨大的背影,不过可以想象他的表情,就像他MSN上那只一脸无辜的“困猫”。尤其是当他看到屏幕上那个数字的时候,就感觉我眼前的这堵“墙”有点微微地颤抖……

“嗨,困猫,你说这是为什么呢?”Young这家伙的确不厚道,明显自己也不知道答案,还在那边摆出一付咄咄逼人的样子。

看来,该我出手了。虽然我比较喜欢“英雄救美”的场景,不过呢,也不能看着新人被欺负而袖手旁观,打击一下Young的嚣张气焰还是很有必要的。

“叫你看的那本《More Effective C++》看得怎么样啦?”我悄悄地走到Young的边上,以一种师傅教训徒弟的语气,冷不丁地冒出了这句话。

“啊?!原来是您老人家啊!”Young一看到我,神情立刻就变了,看来,装清纯也是她的拿手好戏啊,“这个…… 那个…… 我看了一点点……”

“一点点?1个多月的时间,就看了一点点?一本300来页的书,看了这么久,才看了一点点?真是个不用功的孩子,唉,太让我失望了……”对付这家伙,该批的时候就要批一下,而且按照她那种比较喜欢争强好胜的脾气,表现出对她失望,还是很有作用的。

“困猫,你在这里干什么?不去好好地干活,瞎晃悠啥啊?”打压了一下Young的气焰,该将话题引到正题上了。

“这个…… 是这样的啦。刚才Young叫我帮她个忙,然后跑过来,她问了我一个exception的问题,结果有点出乎意料。Weily,你来得正好,帮忙看看吧。”“困猫”开始向我求救了。

我一边点头,一边扫了一下屏幕上的程序。然后顺手拿了桌上的一个文件夹,往Young的脑袋上拍去。结果“啪!”的一下,这声音与我意料中的有点不一样,转头一看,原来Young那家伙早就躲得远远的了,而刚才那一下,是拍到了可怜的“困猫”。

“Young,算你逃得快。你知道吗,这个程序反映的问题,对于你来说,应该不成问题的,这里面牵涉到的东西,在《More Effective C++》中的条款12里有详细的讨论。要是你好好看过的话,就应该明白其中的道理。”

“是吗?师傅啊,那么你就给我讲一下吧,这样可以省掉看书的时间了。我知道要好好看书的啦,只是我一直觉得同样的东西,你一讲我就能明白的,而看书的话,有时候反而会糊涂的。”Young这家伙的另一招拿手好戏就是恭维人。不过,听着还是蛮舒服的。

“那你过来吧这段程序改一下,添加上构造函数、析构函数和拷贝构造函数,用一个成员变量来保存对象的名字,然后在构造函数、析构函数和拷贝构造函数里分别加上打印相关信息的语句,再运行一下看看。”

“哦,好吧。”似乎“困猫”的口头禅成了大家的口头禅了。Young边答应着,边走到她的机器前面,把程序改成了下面这个样子:

#include
#include

using namespace std;

struct Err{
string _name;
Err(string name)
{
cout << “Constructing object: “ << name << endl;
_name = name;
}
Err(const Err& err)
{
cout << “Copying object: “ << err._name << endl;
_name = err._name + “(copy)”;
}
~Err()
{
cout << “Destructing object: “ << _name << endl;
}
};

void f()
{
Err lErr(“local object in f()”);
throw lErr;
}

int main()
{
try{
f();
}
catch(Err& e){
cout << e._name << endl;
}
return 0;
}

“改好了,让我看看结果噢!”Young立刻点了运行,于是,屏幕上出现了下面这个结果:

Constructing object: local object in f()
Copying object: local object in f()
Destructing object: local object in f()
local object in f()(copy)
Destructing object: local object in f()(copy)

“咦?怎么在析构之前,还发生了拷贝操作?而且,catch里面的那个对象,好像是拷贝出来的那个?有点奇怪啊,怎么和函数调用时候的过程不一样呢?”前面一声不响的“困猫”这次似乎发现了关键所在了。嗯,值得肯定他一下。

“你说的没错。这就是调用函数与抛出异常的区别了。就像你们看到的结果,在抛出异常的时候,会先调用拷贝构造函数,将抛出的对象拷贝给一个临时对象,然后析构掉原来的那个对象,再将拷贝生成的临时对象传递给catch。”

“那么传递给catch,而没有再次发生拷贝操作,这是因为用的是catch by reference的方式咯?”Young似乎也发现了问题。很好,这两个家伙一个找到了区别,一个找到了共性,还算孺子可教也。

“没错。所以,如果改成catch by value的方式,那么这里就会发生2次拷贝操作。不相信的话,你们可以试试看。”

“啊?那岂不是用catch by value的方式,对于比较大的exception对象而言,会对效率有较大的影响?”Young依旧是那种点一下走一步的风格,“那么,可不可以用catch by point的方式呢?这种情况下的指针和引用是不是会有区别?”

“你把上面那个程序改一下,试试看吧,所谓实践出真知嘛。”

“哦,好吧”又是一句“困猫”式的回答,而且又是盗版的……

(Young把上面的程序稍作修改,大致是这个样子的:)


void f()
{
Err lErr(“local object in f()”);
throw &lErr;
}

catch(Err* e){
cout << “In Catch…” << endl;
cout << e->_name << endl;
cout << “End of Catch…” << endl;
}

改完程序,Young立马编译运行,结果就看到了下面这些输出:

Constructing object: local object in f()
Destructing object: local object in f()
In Catch…

End of Catch…

“果然如此啊,用指针就没有了拷贝,然后在退出f()函数的时候,那个局部对象被析构掉了,再去访问,就是空的了。原来引用和指针除了使用方法上的不同,还有这么大的区别啊!以前我还以为这两也就是写法不同而已呢。”当Young得到了她预期的东西时,总是显得那么兴奋。

“那么,为什么会这么做呢?为什么要和函数调用返回的规则不同呢?”“困猫”开始变成“十万个为什么了”。

“这个问题很好。函数调用的时候,其最终仍旧会返回到调用函数的地方。所以,在调用时传送一个对象的引用进去,是能够保证函数执行期间,和其返回以后,传进去的那个对象依旧是在生命期内的。而抛出异常的时候就不一样了。抛出异常一般就会退出当前的函数,这样抛出的对象就会无效。而按照引用的规定,它不像指针那样可以存在一个空的指针,也就是不指向任何对象的指针,引用必须有其关联的实体。所以,在throw一个对象的时候,会先拷贝出一个临时对象,再将其传递给catch。这样,即使原来那个对象被释放了,在catch中引用的实际上是那个临时对象,它仍旧存在的。而如果通过catch by value的方式,那么在catch的时候,就会像函数的call by value的方式一样,将那个拷贝出来的临时对象再拷贝给catch,这就是两次拷贝的过程。”

“原来是这个道理。”Young和“困猫”这次达成了一致了,两个家伙异口同声,“我终于明白了。”

“很好。问题解决了,那么各自去忙各自的事情吧。还有,Young,好好看看那本书,别偷懒,下周我要检查你的进度。”

“好,我一定听您老人家的话。”Young又开始嬉皮笑脸地卖乖了。

说了上面那么多话,我也有点渴了。对哦,我不是刚泡好一杯茶嘛。结果回头一拿茶杯,发现茶已经凉掉了。看来离当年“关羽”的境界还有一些差距,算了,再去泡一杯吧。每次碰到Young,似乎都是类似的结果,现在又多了一个“困猫”,唉……

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值