# Item 5: 了解 C++ 為你偷偷地加上和調用了什么函數
作者:Scott Meyers
譯者:fatalerror99 (iTePub's Nirvana)
發布:http://blog.csdn.net/fatalerror99/
一個 empty class(空類)什么時候將不再是 empty class(空類)?答案是當 C++ 搞定了它。如果你自己不聲明一個 copy constructor(拷貝構造函數),一個 copy assignment operator(拷貝賦值運算符)和一個 destructor(析構函數),編譯器就會為這些東西聲明一個它自己的版本。此外,如果你自己根本沒有聲明 constructor(構造函數),編譯器就會為你聲明一個 default constructor(缺省構造函數)。所有這些函數都被聲明為 public 和 inline(參見 Item 30)。作為結果,如果你寫
```
class Empty{};
```
在本質上和你這樣寫是一樣的:
```
class Empty {
public:
Empty() { ... } // default constructor
Empty(const Empty& rhs) { ... } // copy constructor
~Empty() { ... } // destructor — see below
// for whether it's virtual
Empty& operator=(const Empty& rhs) { ... } // copy assignment operator
};
```
這些函數只有在它們被需要的時候才會生成,但是并不需要做太多的事情,就會用到它們。下面的代碼會促使每一個函數生成:
```
Empty e1; // default constructor;
// destructor
Empty e2(e1); // copy constructor
e2 = e1; // copy assignment operator
```
假設編譯器為你寫了這些函數,那么它們做些什么呢?default constructor(缺省構造函數)和 destructor(析構函數)主要是給編譯器一個地方放置 "behind the scenes" code(“幕后”代碼)的,諸如 base classes(基類)和 non-static data members(非靜態數據成員)的 constructors(構造函數)和 destructor(析構函數)的調用。注意,生成的 destructor(析構函數)是 non-virtual(非虛擬)的(參見 Item 7),除非它所在的 class(類)是從一個 base class(基類)繼承而來,而 base class(基類)自己聲明了一個 virtual destructor(虛擬析構函數)(這種情況下,函數的 virtualness(虛擬性)來自 base class(基類))。
對于 copy constructor(拷貝構造函數)和 copy assignment operator(拷貝賦值運算符),compiler-generated versions(編譯器生成版本)只是簡單地從 source object(源對象)拷貝每一個 non-static data member(非靜態數據成員)到 target object(目標對象)。例如,考慮一個 NamedObject template(模板),它允許你將名字和類型為 T 的 objects(對象)聯系起來的:
```
template<typename T>
class NamedObject {
public:
NamedObject(const char *name, const T& value);
NamedObject(const std::string& name, const T& value);
...
private:
std::string nameValue;
T objectValue;
};
```
因為 NamedObject 中聲明了一個 constructors(構造函數),編譯器就不會再生成一個 default constructor(缺省構造函數)。這一點很重要,它意味著如果你小心地設計一個 class(類),使它需要 constructor arguments(構造函數參數),你就不必顧慮編譯器會不顧你的決定,輕率地增加一個不需要參數的 constructors(構造函數)。
NamedObject 既沒有聲明 copy constructor(拷貝構造函數)也沒有聲明 copy assignment operator(拷貝賦值運算符),所以編譯器將生成這些函數(如果需要它們的話)。看,這就是 copy constructor(拷貝構造函數)的用法:
```
NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject<int> no2(no1); // calls copy constructor
```
編譯器生成的 copy constructor(拷貝構造函數)一定會用 no1.nameValue 和 no1.objectValue 分別初始化 no2.nameValue 和 no2.objectValue。nameValue 的類型是 string,標準 string 類型有一個 copy constructor(拷貝構造函數),所以將通過以 no1.nameValue 作為參數調用 string 的 copy constructor(拷貝構造函數)初始化 no2.nameValue。而另一方面,NamedObject<int>::objectValue 的類型是 int(因為在這個 template instantiation(模板實例化)中 T 是 int),而 int 是 built-in type(內建類型),所以將通過拷貝 no1.objectValue 的每一個二進制位初始化 no2.objectValue。
編譯器為 NamedObject<int> 生成的 copy assignment operator(拷貝賦值運算符)本質上也會有同樣的行為,但是,通常情況下,只有在結果代碼合法而且有一個合理的可理解的巧合時,compiler-generated(編譯器生成)的 copy assignment operator(拷貝賦值運算符)才會有我所描述的行為方式。如果這兩項檢測中的任一項失敗了,編譯器將拒絕為你的 class(類)生成一個 operator=。
例如,假設 NamedObject 如下定義,nameValue 是一個 reference to a string(引向一個字符串的引用),而 objectValue 是一個 const T:
```
template<class T>
class NamedObject {
public:
// this ctor no longer takes a const name, because nameValue
// is now a reference-to-non-const string. The char* constructor
// is gone, because we must have a string to refer to.
NamedObject(std::string& name, const T& value);
... // as above, assume no
// operator= is declared
private:
std::string& nameValue; // this is now a reference
const T objectValue; // this is now const
};
```
現在,考慮這里會發生什么:
```
std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2); // when I originally wrote this, our
// dog Persephone was about to
// have her second birthday
NamedObject<int> s(oldDog, 36); // the family dog Satch (from my
// childhood) would be 36 if she
// were still alive
p = s; // what should happen to
// the data members in p?
```
assignment(賦值)之前,p.nameValue 和 s.nameValue 都引向 string objects(對象),雖然并非同一個。那個 assignment(賦值)對 p.nameValue 產生了什么影響呢?assignment(賦值)之后,p.nameValue 所引向的 string 是否就是 s.nameValue 所引向的那一個呢,也就是說,reference(引用)本身被改變了?如果是這樣,就違反了常規,因為 C++ 并沒有提供使一個 reference(引用)引向另一個 objects(對象)的方法。換一種思路,是不是 p.nameValue 所引向的那個 string objects(對象)被改變了,從而影響了其他 objects(對象)—— pointers(指針)或 references(引用)持續指向的那個 string,也就是,賦值中并沒有直接涉及到的對象?這是 compiler-generated(編譯器生成)的 copy assignment operator(拷貝賦值運算符)應該做的事情嗎?
面對這個難題,C++ 拒絕編譯代碼。如果你希望一個包含 reference member(引用成員)的 class(類)支持 assignment(賦值),你必須自己定義 copy assignment operator(拷貝賦值運算符)。對于含有 const members(const 成員)的 classes(類),編譯器會有類似的行為(就像上面那個改變后的 class(類)中的 objectValue)。改變 const members(const 成員)是不合法的,所以編譯器隱式生成的 assignment function(賦值函數)無法確定該如何對待它們。最后,如果 base classes(基類)將 copy assignment operator(拷貝賦值運算符)聲明為 private,編譯器拒絕為從它繼承的 derived classes(派生類)生成 implicit copy assignment operators(隱式拷貝賦值運算符)。畢竟,編譯器為派生類生成的 copy assignment operator(拷貝賦值運算符)也要處理其 base class parts(基類構件)(參見 Item 12),但如果這樣做,它們當然無法調用那些 derived classes(派生類)無權調用的 member functions(成員函數)。
Things to Remember
* 編譯器可以隱式生成一個 class(類)的 default constructor(缺省構造函數),copy constructor(拷貝構造函數),copy assignment operator(拷貝賦值運算符)和 destructor(析構函數)。
- 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 映射