《Effective C++》学习笔记(条款11:在operator=中处理“自我赋值”)

本文探讨了C++编程中自我赋值的问题,如何在编写赋值运算符时避免错误地释放资源。文章介绍了三种解决方法:检查传入参数是否为*this、先复制后删除以及使用拷贝并交换策略。这些方法对于确保对象正确赋值和异常安全至关重要。同时强调了在处理多参数函数时,也要考虑对象别名可能导致的自我赋值情况。
摘要由CSDN通过智能技术生成

最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!

自我赋值”的意思是自己给自己赋值,如:

class Widget{...};

Widget w;
w = w;		//自己给自己赋值

这看起来很蠢,但它合法,所以不要认为它不会发生,此外,赋值动作并不会被你一眼看出来(如w = w;),例如:

Widget arr[10] = {...};
arr[i] = arr[j];	//潜在的自我赋值,arr[i]和arr[j]存储同一个对象或 i == j

Widget w;
Widget* px = &w;
Widget* py = &w;

*px = *py;			//潜在的自我赋值,指针px和py指向同一个对象

这些不明显的自我赋值是由别名造成的,别名就是有一个以上的方法指称某对象。若两个对象处来自同一个继承体系,它们不需要声明为相同类型就可能造成别名,因为基类的引用指针可以指向一个派生类对象。

class Base {...};
class Derived:public Base {...};

void doSomething(const Base& rb,Derived* pd);//rb和*pd有可能其实是同一对象

如果你打算自行管理资源,可能会掉进“在停止使用资源前意外释放了它”的陷阱。假设你建立一个class用来保存一个指向一块动态分配的位图的指针:

class Bitmap{...}
class Widget{
  ...
private:
    Bitmap *bp;
};

Widget& Widget::operator=(const Widget& rhs){
  	delete pb;					//释放当前的bitmap
  	pb = new Bitmap(*rhs.bp);		//使用位图rhs拷贝新的一份位图
  	return *this;					//返回自身引用
}

这里自我赋值的问题是 operator= 函数内的 *this (赋值的目的端)和 rhs 有可能是同一个对象。若真的是同一个对象,则 delete 就不只是释放了当前对象的位图,也释放了 rhs 对象的位图。函数最后的结果是当前对象的位图被释放了,原本期待的结果是不会发生任何变化。

解决方法:

  • 检查传入的参数是不是 *this

    Widget& Widget::operator=(const Widget& rhs){
    	// 先做一个身份校验
    	if(this == &rhs)	return *this;          
    	// 如果不是自己,再执行如下操作
      	delete pb;                   
      	pb = new Bitmap(*rhs.pb);
      	return *this;
    }
    

    这个方法解决了自身赋值的问题,但它不是异常安全的。如果 new Bitmap 导致异常(不论是因为分配时内存不足或是因为 Bitmap 的拷贝构造函数抛出异常),Widget 最终会有一个指向一块被删除的 Bitmap 的指针。

  • 复制位图前别删除

    Widget& Widget::operator=(const Widget& rhs){
    	Bitmap* pOrig = pb;			//暂存原先的pb
        pb = new Bitmap(*rhs.pb);	//令pb指向*rhs.pb的一个拷贝
      	delete pOrig;				//删除原先的pb
      	return *this;
    }
    

    如果 new Bitmap 抛出异常,pb (及其栖身的哪个 Widget)保持原状。即使没有身份校验,它也能够处理自我赋值,因为我们队原位图做了一份拷贝,然后删除原位图,最后指向拷贝的那份位图。

  • 先拷贝再交换(copy and swap)

    class Widget{
      ...
    private:
        Bitmap *bp;
        
        void swap(Widget& rhs);	//把*this的数据和rhs的数据互换
    };
    
    void Widget::swap(Widget& rhs){
        *bp = rhs.bp;
    }
    //引用传递方式
    Widget& Widget::operator=(const Widget& rhs){
    	Widget temp(rhs);	//为rhs数据拷贝一份副本,防止自我赋值
        swap(temp);			//把*this的数据和rhs的数据互换
      	return *this;
    }
    //值传递方式:参数rhs已经是一个副本
    Widget& Widget::operator=(Widget rhs){
        swap(temp);			//把*this的数据和rhs的数据互换
      	return *this;
    }
    

Note:

  • 处理好operator=函数中的自我赋值问题,其中的方法有“检查传入的参数是不是 *this”、“复制位图前别删除”和“先拷贝再交换”
  • 同一个函数中使用多个参数,也需要保证函数再这些参数同时指向同一个对象时可以正常工作

条款12:复制对象时勿忘其每一个成分

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值