首页 > 代码库 > 条款11:在operator = 中处理"自我赋值"
条款11:在operator = 中处理"自我赋值"
条款11:在operator = 中处理"自我赋值"
1、潜在自我赋值
int i = 5; int *x = &i; int *y = &i; *x = *y;
继承类的
class Base{}; class Derived: public Base {}; void DoSomeThing(const Base& base, const Derived &derived) { base = derived; } Derived d; Base *base = &d; DoSomeThing(*base, d); //base和d指向同一个对象,调用会自我赋值一般而言如果某段代码操作pointers或references而它们被用来“指向多个相同类型的对象”,就需要考虑这些对象是否为同一个。实际上两个对象只要来自同一个继承体系,它们甚至不需要声明为相同类型就可能造成“别名”,因为一个base class的reference或pointer可以指向一个derived class对象。
2、自我赋值安全性
自己定义赋值操作符,需要保证“自我赋值安全性”,这需要自己对类中资源管理,而可能会导致“在停止使用资源时意外释放了它”,看一个错误的例子,实现书中的代码
class Bitmap { public: Bitmap(const std::string &option) {this->m_option = option;} Bitmap(const Bitmap &rhs) { this->m_option = rhs.m_option; } std::string GetOption() const { return this->m_option;} private: std::string m_option; }; class Widget { public: Widget() { this->m_b = new Bitmap(""); this->m_count = 0; } ~Widget() { if (m_b != NULL) delete m_b; m_b = NULL; } Widget(const Bitmap &bmap, const int count) { this->m_b = new Bitmap(bmap.GetOption()); this->m_count = count; } Widget(const Widget &rhs) { this->m_b = new Bitmap(*rhs.m_b); this->m_count = rhs.m_count; } Widget& operator = (const Widget &rhs) //错误的赋值函数 { delete this->m_b; this->m_b = new Bitmap(*rhs.m_b); this->m_count = rhs.m_count; return *this; } private: Bitmap *m_b; int m_count; };
Bitmap bmap("fenggao"); Widget wget(bmap, 3); wget = wget; //调用自我赋值,该对象意外释放防止出现这种情况,可以通过“证同测试”,修改赋值函数
Widget& operator = (const Widget &rhs) { if (this == &rhs) return *this; delete this->m_b; this->m_b = new Bitmap(*rhs.m_b); this->m_count = rhs.m_count; return *this; }大多数这种情况已经可以达到“自我赋值安全性”目的,但是分析程序,如果在new Bitmap(*rhs.m_b)中抛出异常(系统没有可分配内存或者Bitmap拷贝构造函数抛出异常),最后Widget的m_b则指向一块被删除的内存,程序出现了不可意料的行为,即“异常安全性”问题出现了。
3、异常安全性
实现程序的异常安全性往往可以解决程序的“自我赋值安全性”,再修改上面的赋值函数。
Widget& operator = (const Widget &rhs) { Bitmap *bt = this->m_b; //临时存取旧的空间 this->m_b = new Bitmap(*rhs.m_b); //分配新的空间并赋值,如果分配内存失败则还是保留旧的内存 this->m_count = rhs.m_count; delete *bt; //释放旧的空间 return *this; }
copy and swap技术
Copy-and-swap可以很好地帮助拷贝赋值操作符达到两个目标:避免代码重复、提供强烈的异常安全保证。
Copy and swap利用拷贝构造函数生成一个临时拷贝,然后使用swap函数将此拷贝对象与旧数据交换。然后临时对象被析构,旧数据消失。我们就拥有了新数据的拷贝。为了使用copy-and-swap,我们需要拷贝构造函数、析构函数以及swap交换函数。一个交换函数是一个non-throwing函数,用来交换某个类的两个对象,按成员交换。我们可能会试着使用std:swap,但是这不可行。因为std:swap使用自己的拷贝构造函数和拷贝赋值操作符。而我们的目的是定义自己的拷贝赋值操作符。
为了实现这种技术,我们需要定义自己的swap函数,现添加swap函数和重新改造赋值函数
friend void Swap(Widget &lrs, Widget &rhs) { std::swap(lrs.m_b, rhs.m_b); std::swap(lrs.m_count, rhs.m_count); } Widget& operator = (const Widget &rhs) { Widget temp(rhs); Swap(*this, temp); return *this; }
上面的赋值函数是参数是pass by reference,而且将参数进行拷贝,这样会导致效率低下,后续条款会详细谈到,我们现在需要遵循的规则:不要拷贝函数参数。你应该按值传递参数,让编译器来完成拷贝工作.重新修改赋值函数如下
Widget& operator = (Widget rhs) { Swap(*this, rhs); return *this; }
记住
1、确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
2、确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
2、确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。