Effective C++条款11:在operator=中处理“自我赋值”(Handle assignment to self in operator=)

  • 条款11:在 operator= 中处理“自我赋值”
    • 类中自我赋值问题及如何解决
    • 异常处理
    • 使用“copy and swap”技术来处理自我赋值
    • 牢记
  • 总结

《Effective C++》是一本轻薄短小的高密度的“专家经验积累”。本系列就是对Effective C++进行通读:


条款11:在 operator= 中处理“自我赋值”

  “自我赋值”发生在对象被赋值给自己时:

class Widget{};Widget w;
w = w;  //自我赋值

  这看起来有点蠢,但它合法,所以不要认定客户绝不会这样做。此外赋值动作并不总是那么可被一眼辨识出来,例如:

a[i] = a[j];

  如果 a 是一个数组,且索引i和j相等,那么也是一个潜在的自我赋值。

*px = *py;

  如果这两个指针px和py指向于同一块内存,那么也是一个潜在的自我赋值

  这些并不明显的自我赋值,是“别名”带来的结果:所谓“别名”就是有一个以上的方法(指涉)某对象。

类中自我赋值问题及如何解决

假设你建立一个类Widget,用来保存一个指针指向一块动态分配的位图(bitmap):

class Bitmap { ... };
class Widget {...
private:Bitmap* pb; // 指针,指向一个从 heap 分配而得的对象
};

  下面是operator=的实现代码,表面上看起来合理,但自我赋值出现时并不安全(也不具备异常安全性)

class Bitmap {};
class Widget {public://赋值运算符Widget&Widget::operator=(const Widget& rhs) // 一份不安全的 operator= 实现版本{delete pb;pb = new Bitmap(*rhs.pb); //是使用rhs's bitmap的副本return *this;}
private:Bitmap* pb;
};

  这里自我赋值的问题是,operator=函数内的 *this (赋值的目的端)和 rhs 有可能是同一个对象,果真如此 delete 就不只是销毁当前对象的 bitmap,它也销毁 rhs 的 bitmap。在函数末尾,Wigdet——它原本不该被自我赋值动作改变的——发现自己持有一个指针指向一个已被删除的对象!

  现在来分析这个运算符如果出现自我赋值而产生的错误:

  • 如果参数rhs传入的就是自身,那么当pb被释放之后,下面再次new的时候又将参数(自己)的pb指针所指的内容传入进去,但是pb的内容已经被释放了,因此再次使用到这个对象的时候就会产生不确定的行为。

错误解决:

  想要阻止这种错误,做法是在赋值运算符最前面的一个“认同测试”达到“自我赋值”的检验目的:
  根据上面“自我赋值”而产生的错误,我们应该在赋值运算符函数的第一步判断传入的对象是否为自己,如果为自己的话做相应的处理

Widget& Widget::operator=(const Widget& rhs)
{//判断是否为“自我赋值”//注意,此处的&为取地址if (this == &rhs)//测试return *this;//其他的与上面介绍的一样delete pb;pb = new Bitmap(*rhs.pb); //以参数为副本调用拷贝构造函数重新创建return *this;
}

异常处理

  上面介绍的operator=虽然解决了“自我赋值”检测,但是不是“异常安全的”。例如在new操作符执行时跑出了异常(内存不足或因为Bitmap类的拷贝构造函数抛出异常),最终Widget对象会只有一个一块已被删除的Bitmap,因此代码是不安全的。

  我们对上面代码进行优化:

Widget& Widget::operator=(const Widget& rhs)
{Bitmap *pOrig = pb; //记住原先的pbpb = new Bitmap(*rhs.pb); //以参数为副本让pb重新创建一个对象delete pOrig;  //删除原先的pbreturn *this;
}

  这段代码可以来处理异常:如果new时抛出了异常,此时我们的pb对象还没有删除

  这段代码还可以来处理“自我赋值”:我们对原bitmap做了一份复制、删除原bitmap,然后将pb再指向于复制的那一份。这个虽然不是处理“自我赋值”最高效的办法,但是行得通。

  关于效率:此处为什么我们不在代码最前面进行“对象是否为自己”的检测了:此处我们的代码已经可以处理自我赋值了,如果还添加“三”中那种“自我检测”的代码,会使代码增多并多了一个语句判断,会使执行速度降低

使用“copy and swap”技术来处理自我赋值

  替换上面的所有办法,我们可以使用“copy and swap”技术来解决“自我赋值”以及“异常处理”

  copy and swap技术和“异常安全性”有密切关系,会在条款29详细讲述

手法1:

class Bitmap {};
class Widget {public:void swap(Widget& rhs); //将参数rhs与*this进行数据交换,详情见条款29Widget& Widget::operator=(const Widget& rhs){Widget temp(rhs);//以函数参数为参数调用Wiget的拷贝构造函数创建一个对象//不能将rhs直接传入swap,因为这样的话会改变=号后面对象的内容,因此上面需要创建一个临时对象tempswap(temp);      //交换参数所指的对象与*thisreturn *this;}
private:Bitmap* pb;
};

手法2:

  实现这种技术的手法还有一种是以“传值调用”operator=,实现如下:

  这种技术手法实现的功能与上面的一样,但是代码没有那么清晰

  但是这种手法将“拷贝”动作从函数体内移动到了“函数参数构造阶段”,因此效率提高了。

class Bitmap {};
class Widget {public:void swap(Widget& rhs); //将参数rhs与*this进行数据交换//rhs是被传对象的一份副本,这样的话我们就不用在operator=为参数创建一个临时对象了Widget& Widget::operator=(Widget rhs){swap(rhs); //将传入的副本与*this进行数据替换return *this;}
private:Bitmap* pb;
};

总的来说:

  • 类的拷贝赋值操作符可能被声明“以 by value 方式接受实参”。
  • 以传值方式传递东西会生成一份副本。

牢记

  • 确保当前对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy=and-swap

  • 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确

总结

期待大家和我交流,留言或者私信,一起学习,一起进步!

Effective C++条款11:在operator=中处理“自我赋值”(Handle assignment to self in operator=)相关推荐

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

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

  2. Effective C++ 11 在operator=中处理“自我赋值” 笔记

    "自我赋值"发生在对象被赋值给自己时: 1 class Widget {...}; 2 Widget w; 3 ... 4 w = w; //赋值给自己 这看起来有点愚蠢,但它合法 ...

  3. 条款11 在operator=中处理“自我赋值”

    "自我赋值"发生在对象被赋值给自己时: 1 class Widget {...}; 2 Widget w; 3 ... 4 w = w; //赋值给自己 这看起来有点愚蠢,但它合法 ...

  4. 条款11:在operator=中处理“自我赋值”

    什么是自我赋值,就是 v = v 这种类型的语句,也许很多人都会说鄙视这种写法,但是如下的写法会不会出现呢? 比如:a[i] = a[j];      // 不巧的是i可能和j相等 *px = *py ...

  5. EC笔记:第二部分:11:在operator=中处理“自我赋值”

    已经一年半没有写过博客了,最近发现学过的知识还是需要整理一下,为知笔记,要开始收费了以前写在为知笔记上笔记也会慢慢的转到博客里. 话不多说,进入正题. 考虑考虑以下场景: 当某个对象对自身赋值时,会出 ...

  6. effective c++条款11扩展——关于拷贝构造函数和赋值运算符

    effective c++条款11扩展--关于拷贝构造函数和赋值运算符 作者:冯明德 重点:包含动态分配成员的类 应提供拷贝构造函数,并重载"="赋值操作符. 以下讨论中将用到的例 ...

  7. C++尽量在operater=中处理“自我赋值”

    operater=中处理"自我赋值 下面的operator=实现是一份不安全的实现,在自赋值时会出现问题: 1.在开头添加"证同测试" c++ 2.通过确保异常安全来获得 ...

  8. effective C++ 条款 11:在operator= 处理‘自我赋值’

    假设建立一个class来保存一个指针指向一块儿动态分配的位图(bitmap) class Bitmap{...}; class Widget { public: protected: private: ...

  9. Effective C++ 条款11_不止于此

    在 operator= 中处理 "自我赋值" " 自我赋值 " 发生在对象被赋值给自己时: class Getself { ... }; Getself w; ...

  10. Effective C++条款(第三版-侯杰译)

    条款一:视C++为一个语言联邦 [C++高效编程守则视情况而变化,取决于你使用的C++哪一部分] 条款二:尽量以const,enum,inline替换#define [对于单纯变量,最好以const对 ...

最新文章

  1. 【自考】信息系统开发与管理(二)——章节详读
  2. BZOJ 1443: [JSOI2009]游戏Game
  3. 数据结构 - 队列简介 及 1个简单的c语言链式队列代码实现
  4. 【Jenkins】持续集成、持续交付与持续部署
  5. mysql 占用的解决_解决 MySQL 突然占用全部内存的问题
  6. 【数据蒋堂】第28期:迭代聚合语法
  7. “我不看春晚,但想要张小斐同款”
  8. Flex控制对主机网页中脚本的访问
  9. 输出最长上升子序列 模型(DP)
  10. 维控触摸屏编程手册_维控触摸屏AB PLC地址编辑说明
  11. 想学一门技术,学java有前途吗?
  12. 手把手教你安装JDK免安装版(简单粗暴)
  13. 基于canvas图像处理的图片 灰色图像
  14. 十种常见领带的打法图解
  15. 网红“小红书”,电商销售新模式
  16. VUE3祖孙组件传值调用方法
  17. android录制视频实现
  18. 查看锐捷poe交换机供电状态_锐捷 RG-S2910-24GT4SFP-UP-H 24个电口支持PoE和PoE+供电交换机...
  19. 手脚老冰凉 妙招来调养
  20. 如何下载蓟州区卫星地图高清版大图

热门文章

  1. 闲置商标转让怎样管理最好?
  2. Python入门到精通———第一天
  3. 你需要一个什么样的网站开发流程?
  4. 时间格式2020-01-13T16:00:00.000Z中的T和Z分别表示什么,如何处理
  5. Artifact xxx:war exploded: Error during artifact deployment. See server log for details.
  6. C语言将raw转为bmp,Raw格式转换为Bmp格式
  7. PLC_自动化控制系统_1_简说自动化控制系统
  8. 基于PLC的搬运机器手控制系统设计
  9. QT设置按钮QPushButton上图片加文字
  10. 用于体外诊断的RPMI 1640培养基(不含氨基酸)