《Effective C++》item11:在operator=中处理“自我赋值”

        “自我赋值”是个看似不可能但却又让人望而生畏的东西,貌似几乎不会有人写出

         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)确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。








你可能感兴趣的:(自我赋值,copy构造函数)