读书笔记 effective c++ Item 11 在operator=中处理自我赋值

1.自我赋值是如何发生的

当一个对象委派给自己的时候,自我赋值就会发生:

1 class Widget { ... };
2
3 Widget w;
4
5 ...
6
7 w = w; // assignment to self、

这看上去是愚蠢的,但这是合法的,所以请放心,客户端是可以这么做的。此外,自身赋值也并不总是很容易的能够被辨别出来。举个例子:

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

上面的代码在i和j相等的情况下就是自我赋值,同样的,看下面的例子:

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

如果px和py恰巧指向同一个东西,那么上面的语句就是自身赋值。这些并不怎么明显的自我赋值是使用别名的结果:也就是使用不止一种方法来指向同一个对象。一般情况下,当我们操作指向不同同类型对象的引用和指针时,需要考虑这些不同的对象是否是同一个对象。事实上,如果两个对象来自同一个继承体系,这两个对象甚至不必声明为同类型的,因为基类的指针或者引用可以指向派生类对象:

1 class Base { ... };
2
3 class Derived: public Base { ... };
4
5 void doSomething(const Base& rb, // rb and *pd might actually be
6
7 Derived* pd); // the same object

2.处理不好自我赋值会使你掉入陷阱

如果你遵循Item13和Item14的建议,你就会使用对象来管理资源,并且你也能够确信对资源进行管理的对象在进行拷贝时会运行的很好。在这种情况下,你的赋值运算符有可能就是自我赋值安全的,而不用去特定的考虑这件事情。如果你尝试自己来管理资源(如果你自己写一个资源管理类这是必须做的),你可能会掉入一个陷阱:在用完某个资源之前,资源突然被释放掉了。举个例子,假设你创建了一个类来管理一个原生指针,这个指针指向动态分配的bitmap对象:

 1 class Bitmap { ... };
 2
 3 class Widget {
 4
 5 ...
 6
 7 private:
 8
 9 Bitmap *pb; // ptr to a heap-allocated object
10
11 };

下面是operator=的一个实现,从表面上看是合理的,但因为自我赋值的存在,实际上它是不安全的。(它也不是异常安全的,我们稍会会处理)

 1 Widget&
 2
 3 Widget::operator=(const Widget& rhs) // unsafe impl. of operator=
 4
 5 {
 6
 7 delete pb; // stop using current bitmap
 8
 9 pb = new Bitmap(*rhs.pb); // start using a copy of rhs’s bitmap
10
11 return *this; // see Item 10
12
13 }

自我赋值的问题出现在operator=内部,*this(赋值目标)和rhs可能是同一个对象。如果这是真的,delete不仅会为当前对象销毁bitmap,也同样会为ths销毁bitmap。在函数的结尾,Widget对象本不应该通过自我赋值有所改变,但你会发现现在它拥有的是一个指向被删除对象的指针!

3.处理自我赋值的方法一:鉴定测试,防止自我赋值

3.1 实现代码

防止这个错误的传统方法是operator=函数的开始进行一个鉴定测试,看是否是一个自我赋值:

 1 Widget& Widget::operator=(const Widget& rhs)
 2
 3 {
 4
 5 if (this == &rhs) return *this; // identity test: if a self-assignment,
 6
 7 // do nothing
 8
 9 delete pb;
10
11 pb = new Bitmap(*rhs.pb);
12
13 return *this;
14
15 }

3.2这个方法的缺陷

这个方法是可以工作的,但是上面已经提到operator=的早先版本不仅是自我赋值不安全的,同样也是异常不安全的(exception-unsafe),在当前版本中关于异常的麻烦会继续存在。特别的,如果”new Bitmap”语句产生一个异常(因为没有足够的内存可以分配或者因为Bitmap的拷贝构造函数抛出一个异常),Widget将会拥有一个指向被删除Bitmap对象的指针。这样的指针是有毒的,因为你不能够安全的释放它们。你甚至不能够安全的读取它们。你唯一能够做的安全的事情就是花费大量的调试的精力来找出问题出在哪里。

4.处理自我赋值的方法二:对语句进行排序

让人高兴的是,使operator=变得异常安全的方法也往往能使其变得自我赋值安全。所以,我们将自我赋值 的问题忽略掉,集中精力去达到异常安全。Item29比较深入的探索了异常安全,在这个条款中,我们只需要观察:对一些语句进行仔细的排序就可以生成exception安全(同样能够达到自我赋值安全)的代码,这就足够了。举个例子,我们只需要注意在对pb指向对象的拷贝完成之前不要将pb释放:

 1 Widget& Widget::operator=(const Widget& rhs)
 2
 3 {
 4
 5 Bitmap *pOrig = pb; // remember original pb
 6
 7 pb = new Bitmap(*rhs.pb); // point pb to a copy of rhs’s bitmap
 8
 9 delete pOrig; // delete the original pb
10
11 return *this;
12
13 }

现在,如果”new BItmap”抛出异常,pb仍然不会发生变化。在没有鉴别测试的情况下,这段代码进行了自我赋值,因为我们将源bitmap做了一份拷贝,让pb去指向拷贝的数据,然后删除源bitmap。这也许不是处理自我赋值的最有效率的方法,但这确实是可行的方法。

如果你关系效率,你可以将鉴别测试的代码重新放回到函数的开始处。但是在这么做之前,问问你自己,自我赋值发生的频率会有多高,因为鉴别测试不是免费的。它会增加一些代码(obj文件也会增大),同时引入了一个流程控制的分支,两者都会使得程序运行速度变慢。Prefetching,caching和pipelining指令的效率都会降低。

5.处理自我赋值的方法三:copy and swap

5.1 实现方法一

我们换一种方法来对operator=中的语句进行手动排序,来同时保证自我赋值和异常安全,这种技术叫做拷贝和交换(copy  and swap)。这种技术与异常安全是紧密相关的,所以会在Item29中描述。然而,它也是实现operator=的一个非常普通的方法,因此值得我们来看看这种实现方法究竟是什么样子:

 1 class Widget {
 2
 3 ...
 4
 5 void swap(Widget& rhs); // exchange *this’s and rhs’s data;
 6
 7 ... // see Item 29 for details
 8
 9 };
10
11 Widget& Widget::operator=(const Widget& rhs)
12
13 {
14
15 Widget temp(rhs); // make a copy of rhs’s data
16
17 swap(temp); // swap *this’s data with the copy’s
18
19 return *this;
20
21 }

5.2 实现方法二

利用下面的两个事实我们可以将上面的实现换一种写法,这两个事实是:(1)一个类的拷贝赋值运算符可以被声明为按值传递。(2)按值传递会对值进行拷贝。下面是另外一种写法:

 1 Widget& Widget::operator=(Widget rhs) // rhs is a copy of the object
 2
 3 { // passed in — note pass by val
 4
 5 swap(rhs); // swap *this’s data with
 6
 7 // the copy’s
 8
 9 return *this;
10
11 }

从个人观点来说,我担心这种方法为了聪明的实现而牺牲了代码的清晰度,但是通过将拷贝操作从函数体内移动到函数的参数中,编译器有时候能够产生更高效的代码,这是事实。

时间: 2024-10-18 11:38:05

读书笔记 effective c++ Item 11 在operator=中处理自我赋值的相关文章

Effective C++ Item 11 在operator= 中处理“自我赋值”

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:确保当对象自我赋值时operator=有良好行为.其中技术包括比较"来源对象"和"目标对象"的地址.精心周到的语句顺序.以及copy-and-swap. 示例:没有"证同测试" #include <iostream> #include <string> using namespace std; class Bi

effective c++ Item 11 在operator=中处理自我赋值

1.自我赋值是如何发生的 当一个对象委派给自己的时候,自我赋值就会发生: 1 class Widget { ... };2 3 Widget w;4 5 ...6 7 w = w; // assignment to self. 这看上去是愚蠢的,但这是合法的,所以请放心,客户端是可以这么做的.此外,自身赋值也并不总是很容易的能够被辨别出来.举个例子: 1 a[i] = a[j]; // potential assignment to self 上面的代码在i和j相等的情况下就是自我赋值,同样的,

Effective C++_笔记_条款11_在operator=中处理“自我赋值”

(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 为什么会出现自我赋值呢?不明显的自我赋值,是“别名”带来的结果:所谓“别名”就是“有一个以上的方法指涉对象”.一般而言如果某段代码操作pointers或references而它们被用来“指向多个相同类型的对象”,就需要考虑这些对象是否为同一个.实际上两个对象来自同一个继承体系,它们甚至不需要声明为相同类型就可能造成“别名”.因为一个base class的refe

Effective C++ 条款11,12 在operator= 中处理&ldquo;自我赋值&rdquo; || 复制对象时不要忘记每一个成分

1.潜在的自我赋值     a[i] = a[j];     *px = *py; 当两个对象来自同一个继承体系时,他们甚至不需要声明为相同类型就可能造成别名. 现在担心的问题是:假如指向同一个对象,当其中一个对象被删,另一个也被删,这会造成不想要的结果. 该怎么办? 比如:   widget& widget:: operator+ (const widget& rhs) {    delete pd;    pd = new bitmap(*rhs.pb);    return *thi

条款11:在operator = 中处理&quot;自我赋值&quot;

条款11:在operator = 中处理"自我赋值" 1.潜在自我赋值 int i = 5; int *x = &i; int *y = &i; *x = *y; 继承类的 class Base{}; class Derived: public Base {}; void DoSomeThing(const Base& base, const Derived &derived) { base = derived; } Derived d; Base *b

用3种方法在 operator= 中处理“自我赋值”

假设你建立一个class 用来保存一个指针指向一块动态分配的位图. 1 class Bitmap {......}; 2 class Widget{ 3 ... 4 private: 5 Bitmap* pb ; 6 }; 1 Widget& Widget::operator= (const Widget& rhs) 2 { 3 delete pb; 4 pb = new Bitmap(*rhs.pb); 5 return *this; 6 } 上面是一份不安全的 operator= 实现

读书笔记 effective c++ Item 45 使用成员函数模板来接受“所有兼容类型”

智能指针的行为像是指针,但是没有提供加的功能.例如,Item 13中解释了如何使用标准auto_ptr和tr1::shared_ptr指针在正确的时间自动删除堆上的资源.STL容器中的迭代器基本上都是智能指针:当然,你不能通过使用“++”来将链表中的指向一个节点的内建指针移到下一个节点上去,但是list::iterator可以这么做. 1. 问题分析——如何实现智能指针的隐式转换 真正的指针能够做好的一件事情是支持隐式转换.派生类指针可以隐式转换为基类指针,指向非const的指针可以隐式转换成为

读书笔记 effective c++ Item 46 如果想进行类型转换,在模板内部定义非成员函数

1. 问题的引入——将operator*模板化 Item 24中解释了为什么对于所有参数的隐式类型转换,只有非成员函数是合格的,并且使用了一个为Rational 类创建的operator*函数作为实例.在继续之前建议你先回顾一下这个例子,因为这个条款的讨论是对它的扩展,我们会对Item 24的实例做一些看上去无伤大雅的修改:对Rational和opeartor*同时进行模板化: 1 template<typename T> 2 class Rational { 3 public: 4 Rati

读书笔记 effective c++ Item 52 如果你实现了placement new,你也要实现placement delete

1. 调用普通版本的operator new抛出异常会发生什么? Placement new和placement delete不是C++动物园中最常遇到的猛兽,所以你不用担心你对它们不熟悉.当你像下面这样实现一个new表达式的时候,回忆一下Item 16和Item 17: 1 Widget *pw = new Widget; 两个函数会被调用:一个是调用operator new来分配内存,第二个是Widget的默认构造函数. 假设第一个调用成功了,但是调用第二个函数抛出了异常.在这种情况下,对步