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