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=中处理自我赋值的更多相关文章

  1. Effective C++ -----条款11: 在operator=中处理“自我赋值”

    确保当对象自我赋值时operator=有良好行为.其中技术包括比较“来源 对象”和“目标对象”的地址.精心周到的语句顺序.以及copy-and-swap. 确定任何函数如果操作一个以上的对象,而其中多 ...

  2. EC读书笔记系列之6:条款11 在operator=中处理自我赋值

    记住: ★确保当对象自我赋值时operator=有良好行为.有三种方法:比较“来源对象”和“目标对象”的地址.精心周到的语句顺序.以及copy-and-swap技术 ★确定任何函数若操作一个以上对象, ...

  3. 11——在operator=中处理自我赋值

    在operator=函数中加一个测试: if(&rhs==this) copy and swap

  4. 读书笔记 effective c++ Item 25 实现一个不抛出异常的swap

    1. swap如此重要 Swap是一个非常有趣的函数,最初作为STL的一部分来介绍,它已然变成了异常安全编程的中流砥柱(Item 29),也是在拷贝中应对自我赋值的一种普通机制(Item 11).Sw ...

  5. 读书笔记 effective c++ Item 21 当你必须返回一个对象的时候,不要尝试返回引用

    1. 问题的提出:要求函数返回对象时,可以返回引用么? 一旦程序员理解了按值传递有可能存在效率问题之后(Item 20),许多人都成了十字军战士,决心清除所有隐藏的按值传递所引起的开销.对纯净的按引用 ...

  6. 读书笔记 effective c++ Item 46 如果想进行类型转换,在模板内部定义非成员函数

    1. 问题的引入——将operator*模板化 Item 24中解释了为什么对于所有参数的隐式类型转换,只有非成员函数是合格的,并且使用了一个为Rational 类创建的operator*函数作为实例 ...

  7. 读书笔记 effective c++ Item 4 确保对象被使用前进行初始化

    C++在对象的初始化上是变化无常的,例如看下面的例子: int x; 在一些上下文中,x保证会被初始化成0,在其他一些情况下却不能够保证.看下面的例子: class Point { int x,y; ...

  8. Effective C++_笔记_条款11_在operator=中处理“自我赋值”

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 为什么会出现自我赋值呢?不明显的自我赋值,是“别名”带来的结果: ...

  9. Effective C++ 条款11:在operator=中处理"自我赋值"

    "自我赋值"发生在对象被赋值给自己时: class Widget { ... }; Widget w; ... w = w; // 赋值给自己 a[i] = a[j]; // 潜在 ...

随机推荐

  1. [转]SQL Server® 2008 R2 Express 静默安装

    1. http://msdn.itellyou.cn/下载Express版SQL Server 2.快捷键win+R,进入CMD,解压文件,解压命令为 <文件名>.exe /x <解 ...

  2. PS基础学习

    1.文件新建 (1).菜单栏新建,快捷键(Ctrl+N) (2).预设的使用--->一般用于网页制作,就选择Web,宽度,高度的单位,网页是像素 (3).分辨率的设置--->电脑网页屏幕分 ...

  3. &简单使用记录

    最近阅读juce代码发现有很多&的用法,例如:(array.size() & 1 == 0) 的判断,仔细分析了下和1做与 操作是为了判断低位是否为0或者1,直观的说就是判断左值的奇偶 ...

  4. IOS 7 Xcode 5 免IDP证书 真机调试(转载)

    最近转开发了,真的很久没有更新博客了,今天有空写一篇吧. 今天带来的是 IOS 7 Xcode 5 免IDP证书的真机调试.说白了就是穷,不想给苹果交那$99的钱. 注意:虽然可以用这个方法实现真机调 ...

  5. php包管理工具最基本的一些问题

    windows下的 1.先安装windows下的set-up程序 点击一步步的, cmd进入,输入composer能成功显示一幅图说明安装成功 2.在下载,https://getcomposer.or ...

  6. onethink的插件扩展

    2014年7月25日 07:22:35 编写一个文章可以同步到微博的插件 思路:首先要进行微博的绑定,获得令牌.然后调用微博的api即可. 2014年7月25日 08:40:27 服务器与本地有区别. ...

  7. android 通过wifi 热点实现手机摄像头数据共享(转)

    原文地址:http://blog.csdn.net/sinat_35845281/article/details/52674946 最近想搞一个新奇的玩意儿~~~ 最近一直在在学习通过两个Androi ...

  8. layout_toLeftOf = “@id/XX” 提示找不到该id的控件

    调布局的时候,需要把一个控件A放在另一个控件B的左边,我的xml布局文件是按照顺序从左到右定义的控件,所以先定义的控件A,然后控件B,在控件A的属性中,定义 android:layout_toLeft ...

  9. Hibernate的一些事儿

    一.Hibernate的工作原理: 1.读取并解析配置文件 2.读取并解析映射信息,创建SessionFactory 3.打开Sesssion 4.创建事务Transation 5.持久化操作 6.提 ...

  10. OC强弱引用的使用规则

    强弱引用的唯一区别只是体现在对象的消亡上. 当一个对象不再有强引用指向它时,它就会被销毁. 弱引用不持有对象,不计入自动引入计数,所以不用考虑它销毁的问题.