读书笔记 effective c++ Item 11 在operator=中处理自我赋值
1.自我赋值是如何发生的
当一个对象委派给自己的时候,自我赋值就会发生:
class Widget { ... }; Widget w; ... w = w; // assignment to self、
这看上去是愚蠢的,但这是合法的,所以请放心,客户端是可以这么做的。此外,自身赋值也并不总是很容易的能够被辨别出来。举个例子:
a[i] = a[j]; // potential assignment to self
上面的代码在i和j相等的情况下就是自我赋值,同样的,看下面的例子:
*px = *py; // potential assignment to self
如果px和py恰巧指向同一个东西,那么上面的语句就是自身赋值。这些并不怎么明显的自我赋值是使用别名的结果:也就是使用不止一种方法来指向同一个对象。一般情况下,当我们操作指向不同同类型对象的引用和指针时,需要考虑这些不同的对象是否是同一个对象。事实上,如果两个对象来自同一个继承体系,这两个对象甚至不必声明为同类型的,因为基类的指针或者引用可以指向派生类对象:
class Base { ... }; class Derived: public Base { ... }; void doSomething(const Base& rb, // rb and *pd might actually be Derived* pd); // the same object
2.处理不好自我赋值会使你掉入陷阱
如果你遵循Item13和Item14的建议,你就会使用对象来管理资源,并且你也能够确信对资源进行管理的对象在进行拷贝时会运行的很好。在这种情况下,你的赋值运算符有可能就是自我赋值安全的,而不用去特定的考虑这件事情。如果你尝试自己来管理资源(如果你自己写一个资源管理类这是必须做的),你可能会掉入一个陷阱:在用完某个资源之前,资源突然被释放掉了。举个例子,假设你创建了一个类来管理一个原生指针,这个指针指向动态分配的bitmap对象:
class Bitmap { ... }; class Widget { ... private: Bitmap *pb; // ptr to a heap-allocated object };
下面是operator=的一个实现,从表面上看是合理的,但因为自我赋值的存在,实际上它是不安全的。(它也不是异常安全的,我们稍会会处理)
Widget& Widget::operator=(const Widget& rhs) // unsafe impl. of operator= { delete pb; // stop using current bitmap pb = new Bitmap(*rhs.pb); // start using a copy of rhs’s bitmap return *this; // see Item 10 }
自我赋值的问题出现在operator=内部,*this(赋值目标)和rhs可能是同一个对象。如果这是真的,delete不仅会为当前对象销毁bitmap,也同样会为ths销毁bitmap。在函数的结尾,Widget对象本不应该通过自我赋值有所改变,但你会发现现在它拥有的是一个指向被删除对象的指针!
3.处理自我赋值的方法一:鉴定测试,防止自我赋值
3.1 实现代码
防止这个错误的传统方法是在operator=函数的开始进行一个鉴定测试,看是否是一个自我赋值:
Widget& Widget::operator=(const Widget& rhs) { if (this == &rhs) return *this; // identity test: if a self-assignment, // do nothing delete pb; pb = new Bitmap(*rhs.pb); return *this; }
3.2这个方法的缺陷
这个方法是可以工作的,但是上面已经提到operator=的早先版本不仅是自我赋值不安全的,同样也是异常不安全的(exception-unsafe),在当前版本中关于异常的麻烦会继续存在。特别的,如果”new Bitmap”语句产生一个异常(因为没有足够的内存可以分配或者因为Bitmap的拷贝构造函数抛出一个异常),Widget将会拥有一个指向被删除Bitmap对象的指针。这样的指针是有毒的,因为你不能够安全的释放它们。你甚至不能够安全的读取它们。你唯一能够做的安全的事情就是花费大量的调试的精力来找出问题出在哪里。
4.处理自我赋值的方法二:对语句进行排序
让人高兴的是,使operator=变得异常安全的方法也往往能使其变得自我赋值安全。所以,我们将自我赋值 的问题忽略掉,集中精力去达到异常安全。Item29比较深入的探索了异常安全,在这个条款中,我们只需要观察:对一些语句进行仔细的排序就可以生成exception安全(同样能够达到自我赋值安全)的代码,这就足够了。举个例子,我们只需要注意在对pb指向对象的拷贝完成之前不要将pb释放:
Widget& Widget::operator=(const Widget& rhs) { Bitmap *pOrig = pb; // remember original pb pb = new Bitmap(*rhs.pb); // point pb to a copy of rhs’s bitmap delete pOrig; // delete the original pb return *this; }
现在,如果”new BItmap”抛出异常,pb仍然不会发生变化。在没有鉴别测试的情况下,这段代码进行了自我赋值,因为我们将源bitmap做了一份拷贝,让pb去指向拷贝的数据,然后删除源bitmap。这也许不是处理自我赋值的最有效率的方法,但这确实是可行的方法。
如果你关系效率,你可以将鉴别测试的代码重新放回到函数的开始处。但是在这么做之前,问问你自己,自我赋值发生的频率会有多高,因为鉴别测试不是免费的。它会增加一些代码(obj文件也会增大),同时引入了一个流程控制的分支,两者都会使得程序运行速度变慢。Prefetching,caching和pipelining指令的效率都会降低。
5.处理自我赋值的方法三:copy and swap
5.1 实现方法一
我们换一种方法来对operator=中的语句进行手动排序,来同时保证自我赋值和异常安全,这种技术叫做拷贝和交换(copy and swap)。这种技术与异常安全是紧密相关的,所以会在Item29中描述。然而,它也是实现operator=的一个非常普通的方法,因此值得我们来看看这种实现方法究竟是什么样子:
class Widget { ... void swap(Widget& rhs); // exchange *this’s and rhs’s data; ... // see Item 29 for details }; Widget& Widget::operator=(const Widget& rhs) { Widget temp(rhs); // make a copy of rhs’s data swap(temp); // swap *this’s data with the copy’s return *this; }
5.2 实现方法二
利用下面的两个事实我们可以将上面的实现换一种写法,这两个事实是:(1)一个类的拷贝赋值运算符可以被声明为按值传递。(2)按值传递会对值进行拷贝。下面是另外一种写法:
Widget& Widget::operator=(Widget rhs) // rhs is a copy of the object { // passed in — note pass by val swap(rhs); // swap *this’s data with // the copy’s return *this; }
从个人观点来说,我担心这种方法为了聪明的实现而牺牲了代码的清晰度,但是通过将拷贝操作从函数体内移动到函数的参数中,编译器有时候能够产生更高效的代码,这是事实。
读书笔记 effective c++ Item 11 在operator=中处理自我赋值的更多相关文章
- Effective C++ -----条款11: 在operator=中处理“自我赋值”
确保当对象自我赋值时operator=有良好行为.其中技术包括比较“来源 对象”和“目标对象”的地址.精心周到的语句顺序.以及copy-and-swap. 确定任何函数如果操作一个以上的对象,而其中多 ...
- EC读书笔记系列之6:条款11 在operator=中处理自我赋值
记住: ★确保当对象自我赋值时operator=有良好行为.有三种方法:比较“来源对象”和“目标对象”的地址.精心周到的语句顺序.以及copy-and-swap技术 ★确定任何函数若操作一个以上对象, ...
- 11——在operator=中处理自我赋值
在operator=函数中加一个测试: if(&rhs==this) copy and swap
- 读书笔记 effective c++ Item 25 实现一个不抛出异常的swap
1. swap如此重要 Swap是一个非常有趣的函数,最初作为STL的一部分来介绍,它已然变成了异常安全编程的中流砥柱(Item 29),也是在拷贝中应对自我赋值的一种普通机制(Item 11).Sw ...
- 读书笔记 effective c++ Item 21 当你必须返回一个对象的时候,不要尝试返回引用
1. 问题的提出:要求函数返回对象时,可以返回引用么? 一旦程序员理解了按值传递有可能存在效率问题之后(Item 20),许多人都成了十字军战士,决心清除所有隐藏的按值传递所引起的开销.对纯净的按引用 ...
- 读书笔记 effective c++ Item 46 如果想进行类型转换,在模板内部定义非成员函数
1. 问题的引入——将operator*模板化 Item 24中解释了为什么对于所有参数的隐式类型转换,只有非成员函数是合格的,并且使用了一个为Rational 类创建的operator*函数作为实例 ...
- 读书笔记 effective c++ Item 4 确保对象被使用前进行初始化
C++在对象的初始化上是变化无常的,例如看下面的例子: int x; 在一些上下文中,x保证会被初始化成0,在其他一些情况下却不能够保证.看下面的例子: class Point { int x,y; ...
- Effective C++_笔记_条款11_在operator=中处理“自我赋值”
(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 为什么会出现自我赋值呢?不明显的自我赋值,是“别名”带来的结果: ...
- Effective C++ 条款11:在operator=中处理"自我赋值"
"自我赋值"发生在对象被赋值给自己时: class Widget { ... }; Widget w; ... w = w; // 赋值给自己 a[i] = a[j]; // 潜在 ...
随机推荐
- ymodem协议c实现(转)
源:ymodem协议c实现 /****************************************Copyright (c)******************************** ...
- 理解javascript函数的重载
javascript其实是不支持重载的,这篇文章会和java语言函数的重载对比来加深对javascript函数重载的理解. 以下我会假设读者不了解什么是重载所以会有一些很基础的概念 ...
- kvm 动态挂载硬盘
根据最新需求需要动态的给kvm下的windows虚拟机挂载硬盘,网上查看了很多资料终于试通了,在这里记录下方便自己回忆,同事可以给大家做做参考,如果有问题欢迎吐槽 环境:先说说我使用的环境,环境是使用 ...
- PHP上传文件大小的修改
采用了plupload来上传文件,但是一直失败. 设置了插件的参数和接受的参数,仍旧失败. 此时想到php.ini中需要修改 post_max_sizeupload_file_size 然后重启服务器
- 子窗口url调整导致父窗口刷新
2014年3月19日 10:22:38 如题: 在弹窗里搜索时,url发生改变,导致父窗口的div消失.为何? 之前的逻辑是隐藏div 现在修改为插入节点 .可是还是刷新字窗口后,父窗口里面的div节 ...
- java系列--抽象类和接口
问题:什么是接口,作用是什么 问题:什么是抽象类,作用是什么 一.抽象类 1.当父类的一些方法不确定时, 2.当一个子类继承的父类是抽象类的话,需要我们把抽象类中所有的抽象方法全部实现 3.抽象方法本 ...
- ajax 页面请求后,jsp页面定位
如下图所示.A,B两区域为不动区域,既不随着滚动条的滚动而移动.C区域为异步加载内容区域, 在C区域中,点击查询按钮,需要异步加载查询 结果.但是查询结果会很长,这样子,就需要向下滑动滚动条,用户体验 ...
- partial类修饰符
partial是一个类修饰符,用于把类定义拆分为几个部分,便于代码管理,如class ClassA{void A(){;}void B(){;}}与partial class ClassA{void ...
- scala和maven整合实践
.scala和maven如何整合 网上有一堆教程讲idea如何new module或new project一步一步来创建scala工程,在这里我不推荐这个.原因是现在主流的开发环境,大多数是采 ...
- 前端开发 Grunt 之 Connect详解
在前端开发过程中,我们需要在开发过程中,将开发中的站点部署到服务器上,然后,在浏览器中查看实际的效果,在 Grunt 环境下,可以直接使用集成在 Grunt 中的 Connect 插件完成站点服务器的 ...