Effective C++ 2e Item17
条款17: 在operator=中检查给自己赋值的情况
做类似下面的事时,就会发生自己给自己赋值的情况:
class X { ... };
X a;
a = a; // a赋值给自己
这种事做起来好象很无聊,但它完全是合法的,所以看到程序员这样做不要感到丝
毫的怀疑。更重要的是,给自己赋值的情况还可以以下面这种看起来更隐蔽的形式
出现:
a = b;
如果b是a的另一个名字(例如,已被初始化为a的引用),那这也是对自己赋值,
虽然表面上看起来不象。这是别名的一个例子:同一个对象有两个以上的名字。在
本条款的最后将会看到,别名可以以大量任意形式的伪装出现,所以在写函数时一
定要时时考虑到它。
在赋值运算符中要特别注意可能出现别名的情况,其理由基于两点。其中之一是效
率。如果可以在赋值运算符函数体的首部检测到是给自己赋值,就可以立即返回,
从而可以节省大量的工作,否则必须去实现整个赋值操作。例如,条款16指出,一
个正确的派生类的赋值运算符必须调用它的每个基类的的赋值运算符,所以在派生
类中省略赋值运算符函数体的操作将会避免大量对其他函数的调用。
另一个更重要的原因是保证正确性。一个赋值运算符必须首先释放掉一个对象的资
源(去掉旧值),然后根据新值分配新的资源。在自己给自己赋值的情况下,释放
旧的资源将是灾难性的,因为在分配新的资源时会需要旧的资源。
看看下面String对象的赋值,赋值运算符没有对给自己赋值的情况进行检查:
class String {
public:
String(const char *value); // 函数定义参见条款11
//
~String(); // 函数定义参见条款11
//
...
String& operator=(const String& rhs);
private:
char *data;
};
// 忽略了给自己赋值的情况
// 的赋值运算符
String& String::operator=(const String& rhs)
{
delete [] data; // delete old memory
// 分配新内存,将rhs的值拷贝给它
data = new char[strlen(rhs.data) + 1];
strcpy(data, rhs.data);
return *this; // see Item 15
}
看看下面这种情况将会发生什么:
String a = "Hello";
a = a; // same as a.operator=(a)
赋值运算符内部,*this和rhs好象是不同的对象,但在现在这种情况下它们却恰巧
是同一个对象的不同名字。可以这样来表示这种情况:
*this data ------------> "Hello