# Item 6: 如果你不想使用 compiler-generated functions(編譯器生成函數),就明確拒絕
作者:Scott Meyers
譯者:fatalerror99 (iTePub's Nirvana)
發布:http://blog.csdn.net/fatalerror99/
房地產代理商出售房屋,服務于這樣的代理商的軟件系統自然要有一個 class(類)來表示被出售的房屋:
```
class HomeForSale { ... };
```
每一個房地產代理商都會很快指出,每一件房產都是獨特的——沒有兩件是完全一樣的。在這種情況下,為 HomeForSale object(對象)做一個 copy(拷貝)的想法就令人不解了。你怎么能拷貝一個獨一無二的東西呢?因此最好讓類似這種企圖拷貝 HomeForSale object(對象)的行為不能通過編譯:
```
HomeForSale h1;
HomeForSale h2;
HomeForSale h3(h1); // attempt to copy h1 — should
// not compile!
h1 = h2; // attempt to copy h2 — should
// not compile!
```
唉,防止這種編譯的方法并非那么簡單易懂。通常,如果你不希望一個 class(類)支持某種功能,你可以簡單地不聲明賦予它這種功能的函數。這個策略對于 copy constructor(拷貝構造函數)和 copy assignment operator(拷貝賦值運算符)不起作用,因為,就象 Item 5 中指出的,如果你不聲明它們,而有人又想調用它們,編譯器就會替你聲明它們。
這就限制了你。如果你不聲明 copy constructor(拷貝構造函數)或 copy assignment operator(拷貝賦值運算符),編譯器也可以替你生成它們。你的 class(類)還是會支持 copying(拷貝)。另一方面,如果你聲明了這些函數,你的 class(類)依然會支持 copying(拷貝)。而我們此時的目的卻是 prevent copying(防止拷貝)!
解決這個問題的關鍵是所有的編譯器生成的函數都是 public(公有)的。為了防止生成這些函數,你必須自己聲明它們,但是你沒有理由把它們聲明為 public(公有)的。相反,應該將 copy constructor(拷貝構造函數)和 copy assignment operator(拷貝賦值運算符)聲明為 private(私有)的。通過顯式聲明一個 member function(成員函數),可以防止編譯器生成它自己的版本,而且將這個函數聲明為 private(私有)的,可以防止別人調用它。
通常,這個方案并不十分保險,因為 member(成員)和 friend functions(友元函數)還是能夠調用你的 private 函數。換句話說,除非你十分聰明地不 define(定義)它們。那么,當有人不小心地調用了它們,在 link-time(連接時)會出現錯誤。這個竅門——聲明 member functions(成員函數)為 private 卻故意不去實現它——確實很好,在 C++ 的 iostreams 庫里,就有幾個類用此方法 prevent copying(防止拷貝)。比如,看一下你用的標準庫的實現中 ios_base,basic_ios 和 sentry 的 definitions(定義),你就會看到 copy constructor(拷貝構造函數)和 copy assignment operator(拷貝賦值運算符)被聲明為 private 而且沒有被定義的情況。
將這個竅門用到 HomeForSale 上,很簡單:
```
class HomeForSale {
public:
...
private:
...
HomeForSale(const HomeForSale&); // declarations only
HomeForSale& operator=(const HomeForSale&);
};
```
你會注意到,我省略了 functions' parameters(函數參數)的名字。這不是必須的,只是一個普通的慣例。畢竟,函數不會被實現,更少會被用到,有什么必要指定參數名呢?
對于上面的 class definition(類定義),編譯器將阻止客戶拷貝 HomeForSale objects(對象)的企圖,如果你不小心在 member(成員)或 friend function(友元函數)中這樣做了,連接程序會提出抗議。
將 link-time error(連接時錯誤)提前到編譯時間也是可行的(早發現錯誤畢竟比晚發現好),通過不在 HomeForSale 本身中聲明 copy constructor(拷貝構造函數)和 copy assignment operator(拷貝賦值運算符)為 private,而是在一個為 prevent copying(防止拷貝)而特意設計的 base class(基類)中聲明。這個 base class(基類)本身非常簡單:
```
class Uncopyable {
protected: // allow construction
Uncopyable() {} // and destruction of
~Uncopyable() {} // derived objects...
private:
Uncopyable(const Uncopyable&); // ...but prevent copying
Uncopyable& operator=(const Uncopyable&);
};
```
為了阻止拷貝 HomeForSale objects(對象),我們現在必須讓它從 Uncopyable 繼承:
```
class HomeForSale: private Uncopyable { // class no longer
... // declares copy ctor or
}; // copy assign. operator
```
這樣做是因為,如果有人——甚至是 member(成員)或 friend function(友元函數)——試圖拷貝一個 HomeForSale objects(對象),編譯器將試圖生成一個 copy constructor(拷貝構造函數)和一個 copy assignment operator(拷貝賦值運算符)。就象 Item 12 解釋的,這些函數的 compiler-generated versions(編譯器生成版)會試圖調用 base class(基類)的相應函數,而這些調用將被拒絕,因為在 base class(基類)中,拷貝操作是 private(私有)的。
Uncopyable 的實現和使用包含一些微妙之處,比如,從 Uncopyable 繼承不必是 public(公有)的(參見 Item 32 和 39),而且 Uncopyable 的 destructor(析構函數)不必是 virtual(虛擬)的(參見 Item 7)。因為 Uncopyable 不包含數據,所以它符合 Item 39 描述的 empty base class optimization(空基類優化)的條件,但因為它是 base class(基類),此項技術的應用不能引入 multiple inheritance(多繼承)(參見 Item 40)。反過來說,multiple inheritance(多繼承)有時會使 empty base class optimization(空基類優化)失效(還是參見 Item 39)。通常,你可以忽略這些微妙之處,而且僅僅像此處演示的這樣來使用 Uncopyable,因為它的工作就像在做廣告。你還可以使用在 Boost(參見 Item 55)中的一個可用版本。那個 class(類)名為 noncopyable。那是一個好東西,我只是發現那個名字有點兒 un-(不……)嗯…… nonnatural(非自然)。
Things to Remember
* 為了拒絕編譯器自動提供的機能,將相應的 member functions(成員函數)聲明為 private,而且不要給出 implementations(實現)。使用一個類似 Uncopyable 的 base class(基類)是方法之一。
- Preface(前言)
- Introduction(導言)
- Terminology(術語)
- Item 1: 將 C++ 視為 federation of languages(語言聯合體)
- Item 2: 用 consts, enums 和 inlines 取代 #defines
- Item 3: 只要可能就用 const
- Item 4: 確保 objects(對象)在使用前被初始化
- Item 5: 了解 C++ 為你偷偷地加上和調用了什么函數
- Item 6: 如果你不想使用 compiler-generated functions(編譯器生成函數),就明確拒絕
- Item 7: 在 polymorphic base classes(多態基類)中將 destructors(析構函數)聲明為 virtual(虛擬)
- Item 8: 防止因為 exceptions(異常)而離開 destructors(析構函數)
- Item 9: 絕不要在 construction(構造)或 destruction(析構)期間調用 virtual functions(虛擬函數)
- Item 10: 讓 assignment operators(賦值運算符)返回一個 reference to *this(引向 *this 的引用)
- Item 11: 在 operator= 中處理 assignment to self(自賦值)
- Item 12: 拷貝一個對象的所有組成部分
- Item 13: 使用對象管理資源
- Item 14: 謹慎考慮資源管理類的拷貝行為
- Item 15: 在資源管理類中準備訪問裸資源(raw resources)
- Item 16: 使用相同形式的 new 和 delete
- Item 17: 在一個獨立的語句中將 new 出來的對象存入智能指針
- Item 18: 使接口易于正確使用,而難以錯誤使用
- Item 19: 視類設計為類型設計
- Item 20: 用 pass-by-reference-to-const(傳引用給 const)取代 pass-by-value(傳值)
- Item 21: 當你必須返回一個對象時不要試圖返回一個引用
- Item 22: 將數據成員聲明為 private
- Item 23: 用非成員非友元函數取代成員函數
- Item 24: 當類型轉換應該用于所有參數時,聲明為非成員函數
- Item 25: 考慮支持不拋異常的 swap
- Item 26: 只要有可能就推遲變量定義
- Item 27: 將強制轉型減到最少
- Item 28: 避免返回對象內部構件的“句柄”
- Item 29: 爭取異常安全(exception-safe)的代碼
- Item 30: 理解 inline 化的介入和排除
- Item 31: 最小化文件之間的編譯依賴
- Item 32: 確保 public inheritance 模擬 "is-a"
- Item 33: 避免覆蓋(hiding)“通過繼承得到的名字”
- Item 34: 區分 inheritance of interface(接口繼承)和 inheritance of implementation(實現繼承)
- Item 35: 考慮可選的 virtual functions(虛擬函數)的替代方法
- Item 36: 絕不要重定義一個 inherited non-virtual function(通過繼承得到的非虛擬函數)
- Item 37: 絕不要重定義一個函數的 inherited default parameter value(通過繼承得到的缺省參數值)
- Item 38: 通過 composition(復合)模擬 "has-a"(有一個)或 "is-implemented-in-terms-of"(是根據……實現的)
- Item 39: 謹慎使用 private inheritance(私有繼承)
- Item 40: 謹慎使用 multiple inheritance(多繼承)
- Item 41: 理解 implicit interfaces(隱式接口)和 compile-time polymorphism(編譯期多態)
- Item 42: 理解 typename 的兩個含義
- Item 43: 了解如何訪問 templatized base classes(模板化基類)中的名字
- Item 44: 從 templates(模板)中分離出 parameter-independent(參數無關)的代碼
- Item 45: 用 member function templates(成員函數模板) 接受 "all compatible types"(“所有兼容類型”)
- Item 46: 需要 type conversions(類型轉換)時在 templates(模板)內定義 non-member functions(非成員函數)
- Item 47: 為類型信息使用 traits classes(特征類)
- Item 48: 感受 template metaprogramming(模板元編程)
- Item 49: 了解 new-handler 的行為
- Item 50: 領會何時替換 new 和 delete 才有意義
- Item 51: 編寫 new 和 delete 時要遵守慣例
- Item 52: 如果編寫了 placement new,就要編寫 placement delete
- 附錄 A. 超越 Effective C++
- 附錄 B. 第二和第三版之間的 Item 映射