为了实现“连锁赋值”,即
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;
}