您的位置:首页 > 编程语言 > C语言/C++

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

2011-09-12 12:19 483 查看
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、确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: