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

来源:互联网 发布:comicstudio mac 编辑:程序博客网 时间:2024/06/06 02:00
 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>
usingnamespace std;
classBase{ };
classDerived:publicBase {};
voiddosomething(constBase &rb, Derived& pb )
{
    cout<<&rb<<endl;
    cout<<&pb<<endl;
}
int_tmain(intargc, _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>
classmyclass{
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(intargc, _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>
usingnamespace std;
classmyclass {
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(intargc, _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、确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

原创粉丝点击