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

来源:互联网 发布:系统优化的目的是 编辑:程序博客网 时间:2024/05/17 22:43

总结:

1.此篇条款主要讲了在自我赋值中可能发存在的一些安全隐患,和推荐了一些解决这些问题的好的operator=的实现方法。

自我赋值如果是程序员自己可能觉得这种弱智的问题怎么会发生在自己身上呢?但是客户可不管你这些,所以首先列举一些不经意间就自我赋值的一些情况。

①最明显的不过于x=x这种自我赋值。

②arr[i]=arr[j],如果i=j这就是一个潜在的自我赋值

③对于不同的指针或者不同的引用表示着同一空间,这个时候也容易产生自我赋值,看下面的例子

int x = 10;int *p = &x;int *p1 = &x;int &pp = x;int &ppp = x;*p = *p1;//自我赋值pp = ppp;//自我赋值
④对于继承体系中也容易产生潜在的自我赋值的情况,因为基类对象的指针和引用可以指向子类对象。例子如下:

class Base{};class Dervice :public Base{};void fun(const Base& s,Dervice *p)
上面这种情况s和p就容易是同一个对象,从而存在自我赋值的隐患。


针对上面的自我赋值情况,你可能会觉得用对象来管理对象的资源,智能指针来管理你的对象,这个时候你的自我赋值或许是安全的不需要去额外操心。(这里说的是用标准库的智能指针来管理你的对象),但是如果你想自己写一个资源管理的类的话,你就需要自己注意在赋值运算符重载中可能会存在的问题。其实说白了就是你自己在写operator=的 时候同样要注意的问题。不过下面我举的例子是用类来管理对象的这种形式来写的,其实就是一个类成员是另一个类对象的这种写法。

①首先看这么一个类的形式

class A{};class B{private:A *pt;};
如果在B的operator=函数中这么实现

B& B::operator=(const B& s){delete _pt;//删除原来的空间_pt = new A(*s._pt);//创建一个*s._pt的副本return *this;}
加入在这个里面存在自我赋值的情况,即*this和s是一个对象的话,那么delete _pt就不仅仅是销毁当前对象的A对象了,他也销毁了s持有的A对象,那么就会造成自己所持有的指针指向了一个被删除的对象
对于这种问题的解决其实很简单,直接在最前面加上判同语句不就可以了

B& B::operator=(const B& s){if (this == &s)return *this;delete _pt;//删除原来的空间_pt = new A(*s._pt);return *this;}
这样就避免了自我赋值所产生的问题。不过虽然他具备了赋值自我赋值所产生的问题,但是并不具备能解决如果异常所产生的问题,比如在new A(*s._pt)的时候抛出了异常,那么_pt就会指向一个被删除的空间。这样的指针是存在很大问题的。你唯一能做的就是慢慢调试去找产生异常的bug吧。所以就又有了下面的版本

B& B::operator=(const B& s){A *p = _pt;//保存A类对象的备份_pt = new A(*s._pt);delete p;//删除原来的A类的对象return *this;}
这段代码我们开始先拷贝一份A,这样的话即使new产生异常,则一切保持不变,即_pt不变,而且这样写还能防止自我赋值,因为加入s和*this相等,我们对原来的已经做了备份,只不过相当于删除yuan原A的对象,又拷贝了一份原A对象,A对象还是没有改变,虽然这种防止自我赋值的方法不是效率最高的但是他也是可行的一种办法。

到此位置我们给出了一个相对完美的operator=的写法,但是还是不够完美,除了②的这种写法还有下面这种方式,就是加入swap,这个swap你可以自己写也可以用库里面的

下面的例子是自己写的

void B::swap(B& rhs){//交换*this和rhs的数据。}B& B::operator=(const B& s){B tmp(s);//用s创建一份临时的对象tmp,出了这个作用域自动被销毁swap(tmp);//通过tmp将s和*this的值交换return *this;}
这种写法还可以演变为下面的这种写法,即将传引用变为传值

void B::swap(B& rhs){//交换*this和rhs的数据。}B& B::operator=( B s){swap(s);//s和*this的值交换return *this;}
最后是不是觉得这种写法更好,但是虽然他很高效但是变的不是那么清晰易读,使有的人不一定一眼就能看出其中的奥秘。

整篇文章给出了几种赋值运算符的写法,自己可以好好品味,当然这种写法也可以试图模仿用在拷贝构造函数身上,需要自己去琢磨了!

阅读全文
0 0