“自我赋值”是个看似不可能但却又让人望而生畏的东西,貌似几乎不会有人写出
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;
}
完美运行!同时,前面那个程序也有同样的问题,必须用delete释放堆空间!
方案三:
使用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)确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。