“自我赋值”是个看似不可能但却又让人望而生畏的东西,貌似几乎不会有人写出
Point p;
p = p;
这样的二逼程序,虽然不会出现编译错误,但确实是个能造成异常的语句!What‘s more,这样的“自我赋值”的机会并不少见,我们来看下面这个例子:
#include "stdafx.h" #include <iostream> using namespace std; class Point{ private: int x,y; public: Point():x(0),y(0){}; void Print(){ cout<< x <<" "<< y <<endl; } }; class Line{ private: Point *p1; public: Line(Point *p):p1(p){}; Line& operator=(const Line& line){ delete p1; p1 = new Point(*line.p1); return *this; } Point* getPoint(){ return p1; } }; int main() { Point p; Line l1(&p), l2(&p); l1 = l2; l1.getPoint()->Print(); return 0; }这是一个典型的“自我赋值”!
首先用p来初始化l1和l2,l1和l2中都有一个Point类型的指针,这个时候他们都指向内存中的同一块内存单元,在接下来执行l1 = l2的时候,会把l2.p1赋值给l1.p1,这就是“自我赋值”了!而Line的copy构造函数的实现过程通常是没有过错的,对指针赋新值的时候先把旧的值给free掉,然后正是这个free确带来了灾难性的后果,程序执行的时候会死掉!
出错的原因很简单,关键是怎么去解决!有三种解决方案:
方案一:
比较来源对象和目标对象的地址。
#include "stdafx.h" #include <iostream> using namespace std; class Point{ private: int x,y; public: Point():x(0),y(0){}; void Print(){ cout<< x <<" "<< y <<endl; } }; class Line{ private: Point *p1; public: Line(Point *p):p1(p){}; Line& operator=(const Line& line){ if (this->p1 == line.p1) //比较来源对象和目标对象的地址来避免“自我赋值” { return *this; } delete p1; p1 = new Point(*line.p1); return *this; } Point* getPoint(){ return p1; } }; int main() { Point p; Line l1(&p), l2(&p); l1 = l2; l1.getPoint()->Print(); return 0; }这种方式的确可以解决“自我赋值”的问题,但是有可能带来新的问题:如果在p1 = new Point(*line.p1)这句出现了exception的话,p1已经给delete掉了,却没有赋予新的值,那么p1最终会持有一个指针指向一块被删除的Point。而方案二却可以很好的解决这个问题。
方案二:
先记住目标指针,等到他被成功的赋值之后再去删除。
这种方式可以有效的解决“自我赋值安全性”和“异常安全性”,值得推荐!
#include "stdafx.h" #include <iostream> using namespace std; class Point{ private: int x,y; public: Point():x(0),y(0){}; void Print(){ cout<< x <<" "<< y <<endl; } }; class Line{ private: Point *p1; public: Line(Point *p):p1(p){}; Line& operator=(const Line& line){ Point* p = p1; //记住原来的p1 p1 = new Point(*line.p1); //令p1指向*p1的一个副本 delete p; //删除原来的值 return *this; } Point* getPoint(){ return p1; } }; int main() { Point p; Line l1(&p), l2(&p); l1 = l2; l1.getPoint()->Print(); return 0; }然而,这个程序并未运行通过,结果死掉了!why?
困惑ing.......................
纠结了一个下午,晚上跟海云同志共同探讨了下这个问题,在大神的火眼金睛下,终于发现了问题:delete只能删除对分配的内存空间,不能针对栈内存!
而程序中的delete p;p是在函数内部定义的,当然不能用delete释放了!
修改了一下程序:
#include "stdafx.h" #include <iostream> using namespace std; class Point{ private: int x,y; public: Point():x(0),y(0){}; void Print(){ cout<< x <<" "<< y <<endl; } }; class Line{ private: Point *p1; //p1一定要指向堆分配的对象 public: Line(Point *p):p1(p){}; Line& operator=(const Line& line){ Point *p = p1; //记住原来的p1 p1 = new Point(*line.p1); //令p1指向*p1的一个副本 delete p; //删除原来的值 return *this; } Point* getPoint() const { return p1; } }; int main() { Point *p = new Point(); //p指向堆分配而来的对象 Line l1(p), l2(p); l1 = l1; l1.getPoint()->Print(); return 0; }
方案三:
使用copy-and-swap技术
#include "stdafx.h" #include <iostream> using namespace std; class Point{ private: int x,y; public: Point():x(0),y(0){}; void Print(){ cout<< x <<" "<< y <<endl; } }; class Line{ private: Point *p1; //p1一定要指向堆分配的对象 public: Line(Point *p):p1(p){}; Line& operator=(const Line& line){ Line temp(line); // 将line的数据制作一份副本 swap(temp); // 将*this数据和temp的数据交换 return *this; } Point* getPoint() const { return p1; } void swap(Line& li) { Point *pp; pp = p1; p1 = li.p1; li.p1 = pp; } }; int main() { Point *p = new Point(); //p指向堆分配而来的对象 Line l1(p), l2(p); l1 = l1; l1.getPoint()->Print(); return 0; }这种方式巧妙的运用的对栈内存在函数调用返回时自动释放的原理,也是值得推荐的!
Remember:
(1)确保当对象自我赋值时operator=有良好行为。其中技术包括:
a.) 比较“来源对象”和“目标对象”的地址);
b.) 精心周到的语句顺序;
c.) 以及copy-and-swap;
(2)确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。