# [12] 賦值算符
## FAQs in section [12]:
* [12.1] 什么是“自賦值”?
* [12.2] 為什么應該當心“自賦值”?
* [12.3] 好,好;我會處理自賦值的。但如何做呢?
## 12.1 什么是“自賦值”?
自賦值就是將對象賦值給本身。例如,
```
?#include?"Fred.hpp"????//?聲明 Fred 類?
?void?userCode(Fred&?x)
?{
???x?=?x;???//?自賦值
?}
```
很明顯,以上代碼進行了顯式的自賦值。但既然多個指針或引用可以指向相同對象(別名),那么進行了自賦值而自己卻不知道的情況也是可能的:
```
?#include?"Fred.hpp"????//?聲明 Fred 類
?void?userCode(Fred&?x,?Fred&?y)
?{
???x?=?y;???//?如果&x?==?&y就可能是自賦值
?}
?int?main()
?{
???Fred?z;
???userCode(z,?z);
?}
```
## 12.2 為什么應該當心“自賦值”?
如果不注意自賦值,將會使你的用戶遭受非常微妙的并且一般來說非常嚴重的bug。例如,如下的類在自賦值的情況下將導致災難:
```
?class?Wilma?{?};
?class?Fred?{
?public:
???Fred()????????????????:?p_(new?Wilma())??????{?}
???Fred(const?Fred&?f)???:?p_(new?Wilma(*f.p_))?{?}
??~Fred()????????????????{?delete?p_;?}
???Fred&?operator=?(const?Fred&?f)
?????{
???????//?差勁的代碼:沒有處理自賦值!
???????delete?p_;????????????????//?Line?#1
???????p_?=?new?Wilma(*f.p_);????//?Line?#2
???????return?*this;
?????}
?private:
???Wilma*?p_;
?};
```
如果有人將 `Fred` 對象賦給其本身,由于`*this`和 `f` 是同一個對象,line #1同時刪除了`this->p_`和`f.p_`。而 line #2使用了已經不存在的對象`*f.p_`,這樣很可能導致嚴重的災難。
作為 `Fred`類的作者,你最起碼有責任確信在`Fred`對象上自賦值是無害的。不要假設用戶不會在對象上這樣做。如果對象由于自賦值而崩潰,那是_你的_過失。
> 另外:上述的`Fred::operator=?(const?Fred&)`還有第二個問題:如果在執行`new?Wilma(*f.p_)`時,拋出了異常或者`Wilma`的拷貝構造函數中的異常), `this->p_`將成為懸空指針——它所指向的內存不再是可用的。這可以通過在刪除就對象前創建對象來解決。
## 12.3 好,好;我會處理自賦值的。但如何做呢?
在你創建類的每時每刻,都應該當心自賦值。這并不意味著需要為你所有的類都增加額外的代碼:只要對象優雅地處理自賦值,而不管是否必須增加額外的代碼。
如果不需要為賦值算符增加額外代碼,這里有一個簡單而有效的技巧:
```
?Fred&?Fred::operator=?(const?Fred&?f)
?{
???if?(this?==?&f)?return?*this;???//?優雅地處理自賦值
//?此處寫正常賦值的代碼...
???return?*this;
?}
```
顯式的測試并不總是必要的。例如,如果修正前一個 FAQ中的賦值算符使之處理`new`拋出的異常和/或`Wilma`類的拷貝構造函數拋出的異常,可能會寫出如下的代碼。注意這段代碼有(令人高興的)自動處理自賦值的附帶效果:
```
?Fred&?Fred::operator=?(const?Fred&?f)
?{
???//?這段代碼優雅地(但隱含的)處理自賦值
???Wilma*?tmp?=?new?Wilma(*f.p_);???//?如果異常在此處被拋出也沒有問題
???delete?p_;
???p_?=?tmp;
???return?*this;
?}
```
在象這個例子的情況下(自賦值是無害的但是低效),一些程序員想通過增加另外的不必要的測試,如“`if?(this?==?&f)?return?*this;`”來改善自賦值時的效率。通常來說,使自賦值情況更高效而使得非自賦值情況更低效的折衷是錯誤的。例如,為`Fred`類的賦值算符增加如上的`if`測試會使得非自賦值情況更低效(一個額外的(而且不必要的)條件分支)。如果自賦值實際上一千次才發生一次,那么 `if`將浪費99.9%的時間周期。
- C++ FAQ Lite
- [1] 復制許可
- [2] 在線站點分發本文檔
- [3] C++-FAQ-Book 與 C++-FAQ-Lite
- [6] 綜述
- [7] 類和對象
- [8] 引用
- [9] 內聯函數
- [10] 構造函數
- [11] 析構函數
- [12] 賦值算符
- [13] 運算符重載
- [14] 友元
- [15] 通過 <iostream> 和 <cstdio>輸入/輸出
- [16] 自由存儲(Freestore)管理
- [17] 異常和錯誤處理
- [18] const正確性
- [19] 繼承 — 基礎
- [20] 繼承 — 虛函數
- [21] 繼承 — 適當的繼承和可置換性
- [22] 繼承 — 抽象基類(ABCs)
- [23] 繼承 — 你所不知道的
- [24] 繼承 — 私有繼承和保護繼承
- [27] 編碼規范
- [28] 學習OO/C++
- [31] 引用與值的語義
- [32] 如何混合C和C++編程
- [33] 成員函數指針
- [35] 模板 ?
- [36] 序列化與反序列化
- [37] 類庫