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(解構函式)。