Effective C++ (E3 10、11)笔记之在operator=中返回*this、处理自我赋值

为了实现“连锁赋值”,即

	Widget wig4,wig3;	
	wig3.printname();
	wig4.printname();
	//continous assigiment
	wig4=wig3=wig1;
	wig3.printname();
	wig4.printname();

operator=必须返回一个reference指向操作符的左侧实参。这项不成文的规定同样适用于其他赋值相关运算,比如+=、-=、*=等等。下面是返回*this的operator=实现:

class Widget{
public:
	Widget(std::string str="")
		:name(str)
	{}
	//void operator=(const Widget& wig)
	//{
	//	name=wig.name;
	//}
	Widget& operator=(const Widget& wig)
	{
		name=wig.name;
		return *this;
	}
	void printname()
	{
		cout<<name<<endl;
	}
private:
	std::string name;
};

如果使用注释掉的operator=,将导致“连锁赋值”编译错误。


所谓“自我赋值”,可能一眼可辨认:

w=w;

也可能是潜在的:

a[i]=a[j];
*px=*py;

尤其是对于资源管理类(有成员指针指向其他对象),如果A=B(B可能就是A),

自我赋值可能会掉进“在赋值给A之前,已经将B对象释放了”的陷阱之中。如下面这个实现:

class TestClass
{
public:
	TestClass(char* ptr)
	{
		pname=ptr;
	}
	TestClass(const TestClass& tc)
	{
		pname=crtstr(tc.pname);	//deep copy
	}
	~TestClass()
	{
		cout<<"call ~TestClass"<<endl;
		if(pname!=NULL){
			cout<<"delete pname"<<endl;
			delete pname;	
			pname=NULL;
		}
	}
	void print()
	{
		if(pname)
			cout<<pname<<endl;
		else
			cout<<"pname is NULL!\n";
	}
	TestClass& operator=(const TestClass& tc)
	{
		delete pname;pname=NULL;
		if(tc.pname==NULL){
			cout<<"tc.pname is NULL!"<<endl;
		}else
			pname=crtstr(tc.pname);
		return *this;
	}
private:
	char* pname;
};

char* crtstr(char* par)
{
	unsigned size=strlen(par);//strlen()不计算\0
	char* tmpptr=new char[size];
	memset(tmpptr,' ',size);//memset(tmpptr,'',10) is forbidden
	memcpy(tmpptr,par,size);
	par[size]='\0';
	return tmpptr;
	
}

这里的operator=不管三七二十一,先delete this的成员指针。如若tc就是this,那tc的成员指针在赋值前已经销毁。测试:

	char* pstr1=new char(10);
	memset(pstr1,' ',10);
	memcpy(pstr1,"MIKE",sizeof("MIKE"));
	TestClass tc1(pstr1);
	tc1.print();
	tc1=tc1;
	tc1.print();

结果:


传统做法是在operator=前面加个“证同测试”。如是同一对象,直接返回*this

	TestClass& operator=(const TestClass& tc)
	{
		if(this==&tc) return *this;
		delete pname;pname=NULL;
		if(tc.pname==NULL)
			cout<<"tc.pname is NULL!"<<endl;
		pname=crtstr(tc.pname);
		return *this;
	}

结果:


这个做法仍有缺点,那便是不具有异常安全性。简单说来,如果某个操作(如new)抛出异常,则pname的状态能否回到有效状态,或者调用operator=之前的状态,或者干脆保证该函数不抛出异常。

其实将语句次序精心安排,就可以得到异常安全代码。即先备份原指针,将原指针指向生成的一个副本,最后删除备份以释放原指针指向的对象。并且此举往往一箭双雕,得到自我赋值安全的回报

	TestClass& operator=(const TestClass& tc)
	{
		//printf("==>%d, %s\n",sizeof(*pname),pname);//output 1,MIKE==>char* result is char,not whole string. so result is 1
		char* ptmp=pname;
		//pname=new char[] , memset(pname,..)... will refresh tc.pname,if tc and this is the same object. Must use a tmp var
		pname=crtstr(tc.pname);	
		delete ptmp;
		return *this;
	}

不过这里要注意,我使用了crtstr函数来new一个副本。按注释所言,在得到这副本前不能对pname进行memset等操作,因为这等于又走回了自我赋值的老路,将tc.pname也memset了。直接返回一个副本的指针就OK!

同时获得异常安全和自我赋值安全的更先进的做法是copy and swap技术。即先复制一个临时(栈)对象,用一个tmp指针指向此临时对象,然后交换tmp指针和pname指针的值。交换后tmp指针指向原资源,因为在栈上自动释放。并且除了复制构造函数外,其他语句都是内置类型操作,而内置类型操作一般不会抛出异常:

	TestClass& operator=(const TestClass& tc)
	{
		TestClass tmpobj(tc);
		char* tmpptr=tmpobj.pname;
		tmpobj.pname=pname;
		pname=tmpptr;
		return *this;
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值