Effective c++学习笔记——条款11:在operateor=中自我赋值

Handle assignment to self in operator=
本条款的核心是关于c++对象的自我赋值,既然说是自我赋值,那么就会产生一些你意想不到的问题。首先看一下很有意思的“自我赋值”,简单例子
// self_opera.cpp : 定义控制台应用程序的入口点。
//2011/9/11-by wallwind-in revenco
#include "stdafx.h"
#include<iostream>
using namespace std;

class myClass { };

int _tmain(int argc, _TCHAR* argv[]) {
myClass my;
my = my;
system("pause");
return 0;
}

上段程序是可以通过的。可能有时候自我赋值是不能一眼就看出来的。比如以下程序语句:
a[i] = a[j]; // 潜在的自我赋值
当i=j的时候,这便是个自我赋值
又如
*px = *py; //潜在的自我赋值
如果*px和*py恰好指向同一个东西,这也是自我赋值;
这些并不明显的自我赋值,是“别名”带来的结果:所谓“别名”就是“有一个以上的方法指称(指涉)某对象”。一般而言如果某段代码操作pointers或references而它们被用来“指向多个相同类型的对象”,就需要考虑这些对象是否为同一个。实际上两个对象只要来自同一个继承体系,它们甚至不需要声明为相同类型就可能造成“别名”,因为一个base class的reference或pointer可以指向一个derived class对象:
就如以下代码:
// self_opera.cpp : 定义控制台应用程序的入口点。
//2011/9/11-by wallwind-in revenco
#include "stdafx.h"
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
class Base{ };
class Derived:public Base {};
void dosomething(const Base &rb, Derived& pb )
{
cout<<&rb<<endl;
cout<<&pb<<endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
const Base rb;
Derived pb;
dosomething(rb,pb);
return 0;
}
输出结果为:
其实自我赋值的情况是很容易出现问题的。
下面给大家举一个比较简单实用的,类似于书中的例子,myclass内部维护char指针类型,并指向一块内存空间,在进行operator操作时,首先释放当前myclass类型buffer所指向的空间,并将buffer指向赋值右边同一空间。如果是指向同一个myclass类型呢?buffer所指向空间已经被释放,调用ToString方法时访问了已释放的空间,其结果未有定义。
// self_opera.cpp : 定义控制台应用程序的入口点。
//2011/9/11-by wallwind-in revenco
#include "stdafx.h"
#include <stdlib.h>
#include <string.h>
class myclass{
public:
myclass() {
buffer = new char[255];
memset(buffer, 65, sizeof(char) * 255);
}
~myclass() {
delete[] buffer;
}
char* ToString() const { return buffer; }
myclass& operator=(const myclass& rhs) {
delete[] buffer;
buffer = rhs.buffer;
return *this;
}
private:
char *buffer;
};
int _tmain(int argc, _TCHAR* argv[]) {
myclass str1;
printf("%sn", str1.ToString());
str1 = str1;
printf("%sn", str1.ToString());
system("pause");
return 0;
}
欲阻止这种错误,传统做法是由operator=最前面的一个“证同测试”达到“自我赋值”的检验目的:
如下代码
myclass& operator=(const String& rhs) {
if (rhs == * this)此处要重写“==”
return *this;
delete[] buffer;
buffer = rhs.buffer;
return *this;
}
这样做行得通。稍早我曾经提过,前一版operator=不仅不具备“自我赋值安全性”,也不具备“异常安全性”,这个新版本仍然存在异常方面的麻烦。比如书中的例子给出这样表述,如果, rhs.buffer;
是一块空地址,或者异常地址,依然会出现以上情况。那么就有了另一种技术保证异常的自我赋值了。
在operator=函数内手工排列语句(确保代码不但“异常安全”而且“自我赋值安全”)的一个替代方案是,使用所谓的copy and swap技术。这个技术和“异常安全性”有密切关系,所以由条款29详细说明。然而由于它是一个常见而够好的operator=撰写办法,所以值得看看其实现手法像什么样子:
// self_opera.cpp : 定义控制台应用程序的入口点。
//2011/9/11-by wallwind-in revenco
#include "stdafx.h"
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
class myclass {
public:
myclass() {
_buffer = new char[255];
memset(_buffer, 65, sizeof(char) * 255);
}
myclass(const myclass& rhs) {
_buffer = new char[255];
memcpy(_buffer, rhs._buffer, sizeof(char) * 255);
}
myclass& operator=(const myclass& rhs) {
myclass temp(rhs);
swap(temp);
return *this;
}
char* ToString() const { return _buffer; }
private:
void swap(const myclass& rhs) {
delete[] _buffer;
_buffer = rhs._buffer;
}
char *_buffer;
};
int _tmain(int argc, _TCHAR* argv[]) {
myclass str1;
printf("%sn", str1.ToString());
str1 = str1;
printf("%sn", str1.ToString());
system("pause");
return 0;
}
我个人比较忧虑这个做法,我认为它为了伶俐巧妙的修补而牺牲了清晰性。然而将“copy 动作”从函数本体内移至“函数参数构造阶段”却可令编译器有时生成更高效的代码。

请记住:

1、确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。

2、确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值