今天我们来讨论操作符重载中比较重要的一个内容:赋值操作符的重载
1. 常量不允许出现在=左边
由于编译期对赋值有着严格的要求和限制,因此强制 operator=() 为成员函数。
成员函数的优点在于在调用时,永远有一个隐式的this指针被调用,而反观友元函数,我们可以将任何两个对象传给友元函数,在下面的例子中,我们会看到这将是一个很大的隐患。
如果我们使用了友元函数进行重载,那很有可能出现诸如
2 = 5 ;
之类的疯狂语句,强制规定的根本原因是在友元函数的情况下用户就可以打破C++的规定,使常量出现在等号的左边。
2. 如何禁止自赋值
创建一个operator=()时,必须从右侧对象中拷贝所有需要的信息到当前对象,已完成对象的赋值。
#include <iostream>
using namespace std ;
class Value{
int a_ , b_ ;
float c_ ;
public :
Value(int a , int b , float c) : a_(a) , b_(b) , c_(c) {}
Value() : a_(0) , b_(0) , c_(0.0) {}
Value operator=(const Value& obj) {
a_ = obj.a_ ;
b_ = obj.b_ ;
c_ = obj.c_ ;
return *this ;
}
friend ostream& operator<<(ostream& os , const Value& obj) ;
};
ostream& operator<<(ostream& os , const Value& obj) {
return os << "a=" << obj.a_ << ", b=" << obj.b_ << ", c=" << obj.c_ << endl ;
}
int main(void) {
Value a , b(1 , 2 , 3.1) ;
a = b ;
cout << a ;
return 0 ;
}
其中有一个十分隐蔽的错误,就是自赋值的问题,我们有可能让一个对象调用operator= 时传进的参数也是自己,这样实际上就会自己给自己赋值。在C++中这是一个很严重的错误,有可能出现在程序结束调用析构函数时,同一块内存空间被释放两次的可怕后果。
因此,应记住一个常识:当我们准备给两个相同类型的对象赋值时,先检查这个对象是否在对自己进行赋值,如果我们不进行检查,就可能产生难以发现的错误。
经过修改的重载函数应该这样去实现
class Value{
int a_ , b_ ;
float c_ ;
public :
Value(int a , int b , float c) : a_(a) , b_(b) , c_(c) {}
Value() : a_(0) , b_(0) , c_(0.0) {}
Value operator=(const Value& obj) {
if(& obj == this) //if there two objects are identity
return *this ; //we should shutdown this function
a_ = obj.a_ ;
b_ = obj.b_ ;
c_ = obj.c_ ;
return *this ;
}
friend ostream& operator<<(ostream& os , const Value& obj) ;
};
如果对象中包含指向其他对象的指针,问题会更加复杂。
这就涉及了深拷贝和浅拷贝的区别。
因为我们如果仅仅使用之前一直介绍的浅拷贝的话,在赋值过程中,A对象指针成员所指向的对象的地址会原封不动的赋值给B对象,两个对象会共同指向同一对象
如果我们使用上面的类,就会出现问题
为了方便起见,画图给大家描述一下
如果只是用简单的浅拷贝,就会使得它们指向同一对象。
因此,我们在实现时,在指针的赋值时,不应该只是简单的赋值,应该创建一个与原对象指针指向对象相同的新对象,再用被赋值对象的指针去指向,才能避免。
class CA
{
public:
char* p;
CA(){p = NULL;};
void Set(char* pStr)
{
delete []p;
if(pStr == NULL)
{
p = NULL;
}
else
{
p = new char[strlen(pStr)+1];
strcpy(p, pStr);
}
};
CA& operator=(CA& a)
{
cout<<” operator = invoked/n”<<endl;
//没有检测自赋值情况
delete []p;
p = a.p;
a.p = NULL;
return *this;
};
~CA(){delete []p;};
};
请见下面的例子代码(例子代码1):
CA a1, a2;
a1.Set(“Ok”);
a2 = a1;
我们的函数看起来工作的很好,但是,请看下面一条语句:
a2 = a2;// 悲剧发生了,a2“拥有”的内存被释放了!
所以,赋值运算符函数应写为下面的形式:
CA& CA::operator=(CA& a)
{
cout<<” operator = invoked/n”<<endl;
//检测自赋值情况
if(this != &a)
{
delete []p;
p = a.p;
a.p = NULL;
}
return *this;
};