上一周课程提到,在拷贝赋值中,需要检测自我赋值,一来是提高自我赋值时的效率,二来避免自我赋值中带有指针时,因为内存的释放导致的其它异常。当时有一个疑问,
自我赋值在实际代码编写中,并不常见,这样的检测,多了一层 if 语句判断,对于不是自我赋值的调用,岂不是降低了效率?
很快,第二周的作业中,又遇到新的问题。
图片上是自己写的cp op= 的实现。嗯,这里按照之前的学习,做了自我赋值的检测,但是,比对老师给出的评分标准,仍然踩到坑了,忘了判断other中的_leftup(Point*类型)指针是否为空。
很显然,这里的判断也是有必要的。吭哧吭哧的重构了自己的代码:
改过之后,似乎也不怕other._leftup== nullptr了。
可是,这才一个指针,就需要这样一堆的 if 做判断,如果Rectangle里边的指针再多一点,一个一个的判断下来,这效率该多底啊!!
有没有什么更好的办法呢?一头雾水。
说来也巧,翻阅《Effective C++》的时候,第29条,
Strive for exception-safe code.
书中提到,异常安全函数(Exception-safe functions)提供三种保证,基本承诺,强烈保证,不抛掷保证(non-throwing)。这里就不具体展开了。同时,书中还提到,有一个copy and swap设计策略,可以提供“强烈保证”。这个,就是这篇文章的重点了。
所谓copy and swap策略,就是从需要修改的对象拷贝(copy)一份,对这个副本进行修改,如果有任何异常,原对象不会有改变状态,如果修改成功,就把修改过的副本和原对象做一个置换(swap)。
按照这个策略,继续重构:
上面两张截图,保留了重构前后的代码。operator=的实现,只需要短短两行代码,other成员的判断,自我赋值的判断,通通不需要了。
这下,之前疑惑的几个问题,自我赋值判断的效率问题,代码冗余,都得到了很好的解决。那么,为什么可以这样呢?
这里其实有一个很重要的细节,参数不是pass by reference,而是pass by value。
我们应该知道,当初老师讲到,参数尽量pass by reference,因为不需要创建一个副本。也就是说,by value的时候,会提供一个副本。也就是说,一旦进入函数体,实际上我们已经调用拷贝构造,完成了一个到other副本的拷贝,这就提供了强烈的异常安全保证:如果拷贝失败,我们不会进入到函数体内,那么this指针所指向的内容也不会被改变。
进入函数体以后,other和this进行交换,this的数据放到临时对象里,这样在函数退出时,旧的数据自动被释放。
PS.以前只听说c++很难,现在发现一个难点所在了,各种各样的原则,策略,有些居然是互相矛盾的。在具体应用时,我们有更多的选择,但如何选择更合适的呢?这才是真正的难点所在。
本人其它笔记: