參加面試的時候,面試官問到這個問題,我的回答:默認構造函數、默認析構函數、默認拷貝構造函數、默認賦值函數。現在回想起來感覺不對,就google,揭示一下這個看似簡單的問題:
一個空的class在C++編譯器處理過後就不再為空,編譯器會自動地為我們聲明一些member function,如果你寫
class A{};
編譯器處理後,就相當於:
class A
{
public:
A(); //默認構造函數
A(const A&); //拷貝構造函數
~A(); //析構函數
A& operator=(const A& rhs);
A* operator&(); //取地址運算符
const A* operator&() const;
};
這種回答對否?
其實對於這樣的一個空類來說,是完全沒有必要的,而編譯器也不是這樣做的。編譯器的做法是:
只有你需要用到這些函數並且你又沒有顯示的聲明這些函數的時候,編譯器才會貼心的自動聲明相應的函數。
比如
A a;
編譯器就會根據上面的實例,給類A生成構造函數和析構函數。
當使用
A b(b);
編譯器就會生成類A的拷貝構造函數。
A c;
c = a;
編譯器生成賦值運算符函數
A &d = a;
編譯器生成取地址運算符函數。
經過我們的分析可以這樣理解:對於一個沒有實例化的空類,編譯器是不會給它生成任何函數的,當實例化一個空類後,編譯器會根據需要生成相應的函數。這條理論同樣適合非空類(只聲明變量,而不聲明函數)。
題外話,方便記住:
其中的默認拷貝賦值操作符只有在生成的代碼合法並且有機會證明它有意義存在時才會生成。這就說明,如果你打算在一個“內含引用成員”或者“內含const成員”的類內支持賦值操作,就必須定義自己的默認拷貝賦值操作符。因為C++本身不允許引用改指不同的對象,也不允許更改const成員。
最後一種情況,當基類將自己的默認拷貝賦值操作符聲明為private時,子類就不會產生自己的的默認拷貝賦值操作符。因為假如產生了這樣的默認拷貝賦值操作符,它會試著去調用基類的默認拷貝賦值操作符去處理基類的部分,不幸的是,它沒有權利。
你可以將拷貝構造函數或默認拷貝賦值操作符聲明為private。這樣明確聲明一個成員函數,就阻止了編譯器暗自創建的默認版本,而這些函數為private,使得可以成功阻止人們調用它。
上面的做法有一個隱患,因為類自身的member和friend還是可以調用這些private函數。有一個很刁鑽的方法,“將成員函數聲明為private而且故意不實現它們”,這樣既阻止了默認函數的生成,而且如果你試著調用這些函數,就會得到一個鏈接錯誤。只聲明,不定義,鏈接器報錯。甚至在聲明的時候,你連參數也不用寫。
只要類裡有指針變量就得自己寫拷貝構造函數和賦值函數,但是你確定用不著這些函數時,可以把這些函數做private聲明而不去實現它,這就防止了會有人去調用它們,也防止了編譯器去生成它們。
如果沒有自定義復制構造函數,則系統會創建默認的復制構造函數,但系統創建的默認復制構造函數只會執行“淺拷貝”,即將被拷貝對象的數據成員的值一一賦值給新創建的對象,若該類的數據成員中有指針成員,則會使得新的對象的指針所指向的地址與被拷貝對象的指針所指向的地址相同,delete該指針時則會導致兩次重復delete而出錯。