## noncopyable
### 頭文件: `"boost/utility.hpp"`
通常編譯器都是程序員的好朋友,但并不總是。它的好處之一在于它會自動為我們提供復制構造函數和賦值操作符,如果我們決定不自己動手去 做的話。這也可能會導致一些不愉快的驚訝,如果這個類本身就不想被復制(或被賦值)。如果真是這樣,我們就需要明確地告訴這個類的使用者復制構造以及賦值 是被禁止的。我不是說在代碼中進行注釋說明,而是說要禁止對復制構造函數以及賦值操作符的訪問。幸運的是,當類帶有不能復制或不能賦值的基類或成員函數 時,編譯器生成的復制構造函數及賦值操作符就不能使用。`boost::noncopyable` 的工作原理就是禁止訪問它的復制構造函數和賦值操作符,然后使用它作為基類。
### 用法
要使用 `boost::noncopyable`, 你要從它私有地派生出不可復制類。雖然公有繼承也可以,但這是一個壞習慣。公有繼承對于閱讀類聲明的人而言,意味著IS-A (表示派生類IS-A 基類)關系,但表明一個類IS-A `noncopyable` 看起來有點不太對。要從`noncopyable`派生,就要包含 `"boost/utility.hpp"` 。
```
#include "boost/utility.hpp"
class please_dont_make_copies : boost::noncopyable {};
int main() {
please_dont_make_copies d1;
please_dont_make_copies d2(d1);
please_dont_make_copies d3;
d3=d1;
}
```
這個例子不能通過編譯。由于`noncopyable`的復制構造函數是私有的,因此對`d2`進行復制構造的嘗試會失敗。同樣,由于`noncopyable`的賦值操作符也是私有的,因此將`d1`賦值給`d3`的嘗試也會失敗。編譯器會給出類似下面的輸出:
```
noncopyable.hpp: In copy constructor
' please_dont_make_copies::please_dont_make_copies (const please_dont_make_copies&)':
boost/noncopyable.hpp:27: error: '
boost::noncopyable::noncopyable(const boost::noncopyable&)' is
private
noncopyable.cpp:8: error: within this context
boost/noncopyable.hpp: In member function 'please_dont_make_copies&
please_dont_make_copies::operator=(const please_dont_make_copies&)':
boost/noncopyable.hpp:28: error: 'const boost::noncopyable&
boost::noncopyable::operator=(const boost::noncopyable&)' is private
noncopyable.cpp:10: error: within this context
```
下一節我們將測試這是如何工作的。很清楚從`noncopyable`派生將禁止復制和賦值。這也可以通過把復制構造函數和賦值操作符定義為私有的來實現。 我們來看一下怎么樣做。
### 使類不能復制
再看一下類 `please_dont_make_copies`, 為了某些原因,它不能被復制。
```
class please_dont_make_copies {
public:
void do_stuff() {
std::cout <<
"Dear client, would you please refrain from copying me?";
}
};
```
由于編譯器生成了復制構造函數和賦值操作符,所以現在不能禁止類的復制和賦值。
```
please_dont_make_copies p1;
please_dont_make_copies p2(p1);
please_dont_make_copies p3;
p3=p2;
```
解決的方法是把復制構造函數和賦值操作符聲明為私有的或是保護的,并增加一個缺省構造函數(因為編譯器不再自動生成它了)。
```
class please_dont_make_copies {
public:
please_dont_make_copies() {}
void do_stuff() {
std::cout <<
"Dear client, would you please refrain from copying me?";
}
private:
please_dont_make_copies(const please_dont_make_copies&);
please_dont_make_copies& operator=
(const please_dont_make_copies&);
};
```
這可以很好地工作,但它不能馬上清晰地告訴 `please_dont_make_copies`的使用者它是不能復制的。下面看一下換成 `noncopyable` 后,如何使得類更清楚地表明不能復制,并且也可以打更少的字。
### 用 noncopyable
類 `boost::noncopyable` 被規定為作為私有基類來使用,它可以有效地關閉復制構造和賦值操作。用前面的例子來看看使用`noncopyable`后代碼是什么樣子的:
```
#include "boost/utility.hpp"
class please_dont_make_copies : boost::noncopyable {
public:
void do_stuff() {
std::cout << "Dear client, you just cannot copy me!";
}
};
```
不再需要聲明復制構造函數或賦值操作符。由于我們是從`noncopyable`派生而來的,編譯器不會再生成它們了,這樣就禁止了復制和賦值。簡潔可以帶來清晰,尤其是象這樣的基本且清楚的概念。對于閱讀這段代碼的使用者來說,馬上就清楚地知道這個類是不能復制和賦值的,因為 `boost::noncopyable` 在類定義的一開始就出現了。最后要提醒的一點是:你還記得類的缺省訪問控制是私有的嗎?這意味著缺省上繼承也是私有的。你也可以象這樣寫,來更加明確這個事實:
```
class please_dont_make_copies : private boost::noncopyable {
```
這完全取決于觀眾;有些程序員認為這種多余的信息是令人討厭并且會分散注意力,而另一些程序員則認同這種清晰性。由你來決定哪一種方法適合你的類和你的程序員。無論哪一種方法,使用 `noncopyable` 都要比"忘記"復制構造函數和賦值操作符的方法更加明確,也比私有地聲明它們更為清晰。
### 記住 the Big Three
正如我們看到的那樣,`noncopyable` 為禁止類的復制和賦值提供了一個方便的辦法。但何時我們需要這樣做呢?什么情況下我們需要自定義復制構造函數或賦值操作符?這個問題有一個通用的答案,一 個幾乎總是正確的答案:無論何時你需要定義析構函數、復制構造函數、或賦值操作符三個中的任意一個,你也需要定義另外兩個\[5\]。它們三者間的互動性非常重要,其中一個存在,其它的通常也都必須要有。我們假設你的一個類有一個成員是指針。你定義了一個析構函數用于正確地釋放空間,但你沒有定義復制構造函數和賦值操作符。這意味著你的代碼中至少存在兩個潛在的危險,它們很容易被觸發。
> \[5\] 這個定律的名字叫the Big Three,來自于C++ FAQs (詳情請見參考書目[2])。
```
class full_of_errors {
int* value_;
public:
full_of_errors() {
value_=new int(13);
}
~full_of_errors() {
delete value_;
}
};
```
使用這個類時,如果你忽視了編譯器為這個類生成的復制構造函數和賦值操作符,那么至少有三種情況會產生錯誤。
```
full_of_errors f1;
full_of_errors f2(f1);
full_of_errors f3=f2;
full_of_errors f4;
f4=f3;
```
注意,第二行和第三行是調用復制構造函數的兩個等價的方法。它們都會調用生成的復制構造函數,雖然語法有所不同。最后一個錯誤在最后一行,賦值操作符使得同一個指針被至少兩個`full_of_errors`實例所刪除。正確的方法是,我們需要自己的復制構造函數和賦值操作符,因為我們定義了我們自己的析構函數。以下是正確的方法:
```
class not_full_of_errors {
int* value_;
public:
not_full_of_errors() {
value_=new int(13);
}
not_full_of_errors(const not_full_of_errors& other) :
value_(new int(*other.value_)) {}
not_full_of_errors& operator=
(const not_full_of_errors& other) {
*value_=*other.value_;
return *this;
}
~not_full_of_errors() {
delete value_;
}
};
```
所以,無論何時,一個類的the big three:復制構造函數、(虛擬)析構函數、和賦值操作符,中的任何一個被手工定義,在決定不需要定義其余兩個之前必須認真仔細地考慮清楚。還有,如果你不想要復制,記得使用 `boost::noncopyable` !
### 總結
有很多類型需要禁止復制和賦值。但是,我們經常忽略了把這些類型的復制構造函數和賦值操作符聲明為私 有的,而把責任轉嫁給了類的使用者。即使你使用了私有的復制構造函數和賦值操作符來確保它們不被復制或賦值,但是對于使用者而言這還不夠清楚。當然,編譯 器會友好地提醒試圖這們做的人,但錯誤來自何處也不是清晰的。最好我們可以清晰地做到這一點,而從 `noncopyable` 派生就是一個清晰的聲明。當你看一眼類型的聲明就可以馬上知道了。編譯的時候,錯誤信息總會包含名字 noncopyable. 而且它也節省了一些打字,這對于某些人而言是關鍵的因素。
以下情形下使用 `noncopyable` :
* 類型的復制和賦值都不被允許
* 復制和賦值的禁止應該盡可能明顯
- 序
- 前言
- Acknowledgments
- 關于作者
- 本書的組織結構
- Boost的介紹
- 字符串及文本處理
- 數 據結構, 容器, 迭代器, 和算法
- 函數對象及高級編程
- 泛 型編程與模板元編程
- 數學及數字處理
- 輸入/輸出
- 雜項
- Part I: 通用庫
- Library 1. Smart_ptr
- Smart_ptr庫如何改進你的程序?
- 何時我們需要智能指針?
- Smart_ptr如何適應標準庫?
- scoped_ptr
- scoped_array
- shared_ptr
- shared_array
- intrusive_ptr
- weak_ptr
- Smart_ptr總結
- Library 2. Conversion
- Conversion 庫如何改進你的程序?
- polymorphic_cast
- polymorphic_downcast
- numeric_cast
- lexical_cast
- Conversion 總結
- Library 3. Utility
- Utility 庫如何改進你的程序?
- BOOST_STATIC_ASSERT
- checked_delete
- noncopyable
- addressof
- enable_if
- Utility 總結
- Library 4. Operators
- Operators庫如何改進你的程序?
- Operators
- 用法
- Operators 總結
- Library 5. Regex
- Regex庫如何改進你的程序?
- Regex 如何適用于標準庫?
- Regex
- 用法
- Regex 總結
- Part II: 容器及數據結構
- Library 6. Any
- Any 庫如何改進你的程序?
- Any 如何適用于標準庫?
- Any
- 用法
- Any 總結
- Library 7. Variant
- Variant 庫如何改進你的程序?
- Variant 如何適用于標準庫?
- Variant
- 用法
- Variant 總結
- Library 8. Tuple
- Tuple 庫如何改進你的程序?
- Tuple 庫如何適用于標準庫?
- Tuple
- 用法
- Tuple 總結
- Part III: 函數對象與高級編程
- Library 9. Bind
- Bind 庫如何改進你的程序?
- Bind 如何適用于標準庫?
- Bind
- 用法
- Bind 總結
- Library 10. Lambda
- Lambda 庫如何改進你的程序?
- Lambda 如何適用于標準庫?
- Lambda
- 用法
- Lambda 總結
- Library 11. Function
- Function 庫如何改進你的程序?
- Function 如何適用于標準庫?
- Function
- 用 法
- Function 總結
- Library 12. Signals
- Signals 庫如何改進你的程序?
- Signals 如何適用于標準庫?
- Signals
- 用法
- Signals 總結