***************************************************
更多精彩,歡迎進入:http://shop115376623.taobao.com
***************************************************
定義一個空的C++類,例如
class Empty
{
}
一個空的class在C++編譯器處理過后就不再為空,編譯器會自動地為我們聲明一些member function,一般編譯過去就相當于
class Empty
{
public:
Empty(); // 缺省構造函數
Empty( const Empty& ); // 拷貝構造函數
~Empty(); // 析構函數
Empty& operator=( const Empty& ); // 賦值運算符
Empty* operator&(); // 取址運算符
const Empty* operator&() const; // 取址運算符 const
};
一般的書上好像都是前面四種:默認構造函數,拷貝構造函數,默認賦值函數以及析構函數,后面兩種其實屬于,但要需要注意的是,只有當你需要用到這些函數的時候,編譯器才會去定義它們。
默認的析構函數是非虛函數(除非基類有自己聲明的虛析構函數)。而拷貝默認構造函數和默認拷貝賦值操作符知識是單純將來源對象的每一個非靜態成員拷貝到對象目標中(bitwise copy)。
其中的默認拷貝賦值操作符只有在生成的代碼合法并且有機會證明它有意義存在時才會生成。這就說明,如果你打算在一個“內含引用成員”或者“內含const成員”的類內支持賦值操作,就必須定義自己的默認拷貝賦值操作符。因為C++本身不允許引用改指不同的對象,也不允許更改const成員。
最后一種情況,當基類將自己的默認拷貝賦值操作符聲明為private時,子類就不會產生自己的的默認拷貝賦值操作符。因為假如產生了這樣的默認拷貝賦值操作符,它會試著去調用基類的默認拷貝賦值操作符去處理基類的部分,不幸的是,它沒有權利。
你可以將拷貝構造函數或默認拷貝賦值操作符聲明為private。這樣明確聲明一個成員函數,就阻止了編譯器暗自創建的默認版本,而這些函數為private,使得可以成功阻止人們調用它。
上面的做法有一個隱患,因為類自身的member和friend還是可以調用這些private函數。有一個很刁鉆的方法,“將成員函數聲明為private而且故意不實現它們”,這樣既阻止了默認函數的生成,而且如果你試著調用這些函數,就會得到一個鏈接錯誤。只聲明,不定義,鏈接器報錯。甚至在聲明的時候,你連參數也不用寫。
而試著將上述的鏈接器錯誤提前到編譯器也是可以的。我們專門設計一個類Unconpyable。
--------------------------------------------------------------------
class Uncopybale {
protected:
?
?
private:
?
?
};
--------------------------------------------------------------------
為了阻止對象被拷貝,我們唯一需要做的就是繼承Uncopyable。這些函數的默認生成版本會嘗試調用其基類的對應版本,那些調用會被編譯器拒絕,因為它基類的拷貝函數是private。
Boost提供的noncopyable類也有類似的功能。
忠告:
為了駁回編譯器自動提供的技能,可將相應的成員函數聲明為private并且不予實現。使用像Uncopyable這樣的基類也是一種做法。
并不是所有的編譯器都包成對象的內置類型成員會被自動初始化為0。永遠在使用對象之前先將它初始化。確保每一個構造函數都將對象的每一個成員初始化。 別把賦值錯當成初始化。C++規定,對象的成員變量的初始化動作發生在進入構造函數本體之前(對于內置類型對象可能不確定),這點對于非內置類型對象來說尤其關鍵。如果你沒有在成員初始化列表(member initialization list)為其初始化,它們將調用自己的默認構造函數,然后才進入構造函數內部(很可能你會在這里給他們賦值)。在成員初始化列表中的初始化只是調用了拷貝構造函數一次,而在構造函數內部再為其賦值則在調用默認構造函數后又調用了一次拷貝構造函數。哪個效率高你當然知道。 所以,請用成員初始化列表進行初始化,雖然效率提高只針對于非內置類型成員,但是規定總是在初值列中雷楚所有成員變量,這樣就省的有些未被列出的內置類型成員被忘記初始化。而有些時候,即使成員變量是內置類型,也必須要用成員初始化列表(成員變量為const或者reference,它們一定要有初值,而且不能被賦值)。 總之,總是使用成員初始化列表,這樣或者必要,或者高效。有個例外,當你重載多個構造函數,每個構造函數有很多成員變量和基類的時候(這意味這成員初始化列表會很多、很長而且重復較多),可以將一些內置類型變量的初始化動作(它們的賦值和初始化不影響效率)移到一個私有函數中,供所有的構造函數調用。 規定:初始化順序是基類早于派生類,類成員變量則以其聲明順序為準。所以成員初始化列表中列出的各個成員的順序最好與聲明的順序相同。 最后說個不常見的問題:某個對象A的非靜態成員變量初始化動作正好使用了另外一個編譯單元(另外一個cpp)中的某個非靜態對象B,你不能保證A在需要B的時候,B就已經被編譯好而且產生了。解決的辦法是將對象A和對象B都分別放到函數中(貌似是專門為每個這樣的對象定制的對象),并且聲明為static。這些函數返回的是靜態對象的引用。這是單例模式的一種實現。在程序中以前需要對象引用的地方直接調用這些函數就好了。這種reference-returning函數對于處理多線程環境下的“競速形勢(race conditions”的方法是:在程序的單線程啟動階段手工調用所有的reference-returning函數。 忠告: 1 為內置對象進行手工初始化,因為C++不保證初始化它們。 2 構造函數最好使用成員初始化列表(member initialization list),而不要在構造函數本體內使用賦值操作。初始化列表列出的成員變量,其排列次序應該和它們在class中的聲明次序相同。 3 為免除“跨編譯單元的初始化次序”問題,請以local static對象替換non-local static對象。 |