选择显示字体大小

c++箴言:在operator= 中处理自赋值


  一个对象赋值给自己的时候就发生了一次自赋值:

class widget { ... };

widget w;
...

w = w; // assignment to self

  这看起来很愚蠢,但它是合法的,所以应该确信客户会这样做。另外,赋值并不总是那么容易辨别。例如,

a[i] = a[j]; // potential assignment to self

  如果 i 和 j 有同样的值就是一个自赋值,还有

*px = *py; // potential assignment to self

  如果 px 和 py 碰巧指向同一个东西,这也是一个自赋值。这些很不明显的自赋值是由别名(有不止一个方法引用一个对象)造成的。通常,当引用或者指针指向相同类型的多个对象时,对这些引用和指针进行操作的代码需要考虑到那些对象可能是相同的。实际上,如果两个对象来自同一个继承体系,甚至不需要公开声明,它们就是相同类型的,因为一个基类的引用或者指针也能够引用或者指向派生类类型的对象:

class base { ... };

class derived: public base { ... };

void dosomething(const base& rb, // rb and *pd might actually be
derived* pd); // the same object

  如果你遵循 item 13 和14 的建议,你应该总是使用对象来管理资源,而且你应该确保那些资源管理对象(resource-managing objects)被拷贝时行为良好。如果是这种情况,你的赋值运算符在你没有考虑自拷贝的时候可能也是自赋值安全(self-assignment-safe)的。但是,如果你试图自己管理资源,无论如何(如果你写一个资源管理类(resource-managing class),你当然不得不做),你可能会落入在你使用完一个资源之前就已意外地将它释放的陷阱。例如,假设你创建了一个类,它持有一个指向动态分配 bitmap 的指针:

class bitmap { ... };

class widget {
 ...
 private:
  bitmap *pb; // ptr to a heap-allocated object
};

  下面是一个 operator= 的实现,表面上看它是合理的,但如果出现自赋值则是不安全的。(它也不是异常安全(exception-safe)的,但我们要过一会儿才会涉及到它。)

widget&
widget::operator=(const widget& rhs) // unsafe impl. of operator=
{
 delete pb; // stop using current bitmap
 pb = new bitmap(*rhs.pb); // start using a copy of rhs’s bitmap

 return *this; // see item 10
}

  这里的自赋值问题在 operator= 的内部,*this(赋值的目标)和 rhs 不能是同一个对象。如果它们是,则那个 delete 不仅会销毁当前对象的 bitmap,也会销毁 rhs 的 bitmap。在函数的结尾,widget——通过自赋值应该没有变化——发现自己持有一个指向已删除对象的指针。

  防止这个错误的传统方法是在 operator= 的开始处通过一致性检测来阻止自赋值:

widget& widget::operator=(const widget& rhs)
{
 if (this == &rhs) return *this; // identity test: if a self-assignment,
 // do nothing
 delete pb;
 pb = new bitmap(*rhs.pb);

 return *this;
}

  这个也能工作,但是我在前面提及那个先前版本的 operator= 不仅仅是自赋值不安全(self-assignment-unsafe),它也是异常不安全(exception-unsafe)的,而且这个版本还是有异常上的麻烦。详细地说,如果 "new bitmap" 表达式引发一个异常(可能因为供分配的内存不足或者因为 bitmap 的拷贝构造函数抛出一个异常),widget 将以持有一个被删除的 bitmap 的指针而告终。这样的指针是有毒的,你不能安全地删除它们。你甚至不能安全地读取它们。你对它们唯一能做的安全的事情就是花费大量的调试精力来断定它们起因于哪里。

  碰巧的是,使 operator= 异常安全(exception-safe)也同时弥补了它的自赋值安全(self-assignment-safe)。这就导致了更加通用的处理自赋值问题的方法就是忽略它,而将焦点集中于取得异常安全。item 29 更加深入地探讨了异常安全,但是在本 item 中,已经足够我们在很多情况下来观察它,仔细地调整一下语句的顺序就可以得到异常安全(exception-safe)(同时也是自赋值安全)的代码。例如,在这里,我们只要注意直到我们拷贝了 pb 指向的目标之后,才能删除它:

widget& widget::operator=(const widget& rhs)
{
 bitmap *porig = pb; // remember original pb
 pb = new bitmap(*rhs.pb); // make pb point to a copy of *pb
 delete porig; // delete the original pb

 return *this;
}

  现在,如果 "new bitmap" 抛出一个异常,pb(以及它所在的 widget)的遗迹没有被改变。甚至不需要一致性检测,代码也能处理自赋值,因为我们为原先的 bitmap 做了一个拷贝,删除原先的 bitmap,然后指向我们作成的拷贝。这可能不是处理自赋值的最有效率的做法,但它能够工作。

  如果你关心效率,你可以在函数开始处加上一致性检测。在这样做之前,无论如何,先问一下自己,你认为自赋值发生的频率有多大,因为检测并不是免费午餐。他将使代码(源代码和目标代码)有少量增大,而且他将在顺序流程中引入一个分支,这两点会减慢运行速度。例如,指令预读取(instruction prefetching),高速缓存(caching)和流水线操作(pipelining)的效力都将被降低。

  在 operator= 中另一个可选择的,确保实现是异常安全和自赋值安全的,手动排序语句的技术被称为 "copy and swap"。这一技术和异常安全关系密切,所以将在 item 29 中描述。无论如何,这是一个写 operator= 的足够通用的方法,值得一看,这样一个实现看起来通常就像下面这样:

class widget {
 ... 
 void swap(widget& rhs); // exchange *this’s and rhs’s data;
 ... // see item 29 for details
};

widget& widget::operator=(const widget& rhs)
{
 widget temp(rhs); // make a copy of rhs’s data

 swap(temp); // swap *this’s data with the copy’s
 return *this;
}

  在这个主题上的一个变种利用了如下事实:(1)一个类的拷贝赋值运算符可以被声明为传值参数;(2)通过传递一些东西的值来做出它的拷贝:

widget& widget::operator=(widget rhs) // rhs is a copy of the object
{ // passed in - note pass by val

 swap(rhs); // swap *this’s data with
 // the copy’s
 return *this;
}

  对我本人来说,我担心这个方法会搅乱一些聪明的头脑,但是通过将拷贝操作从函数体中移到参数的构造中,它有时确实能使编译器产生更有效率的代码。

  things to remember

  ·当一个对象自赋值的时候,确保 operator= 能行为良好。技巧包括比较源对象和目标对象的地址,关注语句顺序,和 copy-and-swap

  ·如果两个或更多对象相同,确保任何操作多于一个对象的函数行为正确。


 


关键字 本文所属关键字

相关 与本文相关文章

分类 所有文章关键字导航

源码编程相关

Java   Asp   PHP   .Net   XML   C/C++   CGI   VB   Jsp   J2ee   J2se   J2me   EJB   Servlet   Tomcat   Resin   Struts   Weblogic   Eclipse   ANT   GUI   JMS   Web servise   IDEA   Webphere   Hibernate   Spring   Jboss   Applet   Swing   Socket   Javamail   Perl   Ajax   P2P   安全   模式   框架   测试   开源   游戏

SQL数据库相关

My-SQL   Ms-SQL   Access   DB2   Oracle   Sybase   SQLserver   索引   存储过程   加密   数据库   分页   视图  

手机无线相关

3G   Wap   CDMA   GRPS   GSM   IVR   彩信   短信   无线   增值业务

网页设计制作相关

HTML   CSS   网页配色   网页特效   Javascript   VBscript   Dreamweaver   Frontpage   JS   Web   网站设计

网站建设推广相关

建站经验   网站优化   网站排名   推广   Alexa

操作系统/服务器相关

Windows XP   Windows 2000   Windows 2003   Windows Me   Windows 9.x   Linux   UNIX   注册表   操作系统   服务器   应用服务器

图形图像多媒体相关

Photoshop   Fireworks   Flash   Coreldraw   Illustrator   Freehand   Photoimpact   多媒体   图形图像

标准 网站致力的规范

Valid CSS!

无不良内容,无不良广告,无恶意代码

Valid XHTML 1.0 Transitional

creativecommons