編寫的每個類幾乎都有乙個或多個建構函式、乙個析構函式和乙個賦值運算子。這些是編寫乙個類所必需的一些函式,控制著類的基本操作,如產生物件並初始化,以及從系統中排除舊物件並對其進行恰當的清理工作,還有賦予物件新值。在這些函式中出錯帶來很大的負面影響,所以正確地寫好這些函式是十分重要的。這些函式構成了類的中樞神經。這一章中將為你介紹怎樣編寫這些程式才會使你的類更加優秀。
在 c++ 處理過之後,什麼時候空類不再是個空類呢?對於乙個類來說,如果你自己不手動宣告,編譯器就會為它宣告(編譯器版本的)乙個copy建構函式、乙個賦值運算子、和乙個析構函式。而且,在沒有宣告建構函式的情況下,編譯器也將為你宣告乙個預設建構函式。所有這些函式是public 的並且是inline 的(參見第 30 條)。舉例如下:
class empty{};
它在本質上和下面這個類等價
class empty
//default建構函式
empty(constempty& emy) //copy建構函式
~empty(){...} //析構函式 ,下文將分析它是否為虛函式
empty&operator=(const empty& emy) //賦值運算子
};
只有這些函式需要被呼叫時才會生成,但是需要他們是經常的事情。以下的**可以生成每乙個函式:
empty e1; //預設建構函式,析構函式
empty e2(e1); //複製建構函式
e2=e1; //賦值運算子
現在我們知道編譯器編寫了這些函式,那這些函式做了什麼呢?default構造器和析構器主要作用是給編譯器乙個地方來放置「幕後**」,就像呼叫基類和非靜態資料成員的建構函式和析構函式。請注意,對於由編譯器生成的析構函式,它是non-virtual的,(參 見第 7 條)。
除非這個類繼承自乙個擁有虛析構函式的基類(這個情況下,析構函式的虛擬性來自它的基類)。
對於複製構造器和賦值運算子而言,編譯器所生成的版本僅僅將原物件的非靜態資料成員拷貝到目標物件。請參見下邊的 namedobject 模板,它允許你將乙個個名稱和型別為t的物件產生關聯:
tmplateclass nameobject
;
由於類中宣告了乙個構造器,編譯器不再為它生成乙個預設構造器。這一點很重要。這意味著這個類已經經過你的設計,其建構函式要求實參,這時便不需要擔心編譯器會在你的類中新增乙個無參構造器(即default建構函式)覆蓋掉你的版本。
namedobject 沒有宣告複製構造器和賦值運算子,所以編譯器將會自動生成這些函式(如果被呼叫的話)。請看下面**中複製構造器的應用:
namedobjectn1("smallest prime number", 2);
namedobjectn2(n1) ; // 呼叫複製構造器
由編譯器自動生成的這一複製構造器必須要分別使用 n1.namevalue 和 n1.objectvalue 來初始化 n2.namevaule 和 n2.objectvalue 。 namevalue 是乙個 string ,由於標準字串型別帶有乙個複製構造器,所以 n2.namevalue 將通過呼叫 string 的複製構造器(以 n1.namevalue 作為其引數)得到初始化。另外, namedobject::objectvalue 是 int 型的(這是因為對於當前的模板例項來說, t 是 int 型的),而 int 是乙個內建型別,所以 n2.objectvalue 將通過複製 n1.objectvalue 來得到初始化。
由編譯器自動生成的namedobject的賦值運算子與上述的複製構造器在本質上說行為相同,但一般而言,編譯器會評估生成**是否合法,是否有存在的價值,這兩者是賦值運算子生成的前提。如果其中任意一條無法滿足,編譯器將會拒絕為你的類生成乙個 operator= 。
請看下邊的示例,如果namedobject 被定義成這樣,namevalue 是乙個指向字串的引用,而objectvalue 是乙個 const t :
templateclass nameobject ;
現在請你思考接下來會發生什麼事情:
std::string new("abc");
std::string old("def");
namedobjectp(old, 2);
namedobjects(old, 36);
p = s; // 對於 p 中的資料成員將會發生什麼變化
在賦值之前, p.namevalue 和 s.namevalue 都引用了乙個 string 物件,儘管不是同乙個。那麼賦值操作又怎麼會影響到 p.namevalue 呢?賦值之後, p.namevalue 應該指向 s.namevalue 所指的那個string? 換句話說,引用是否可以被更改?如果可以的話,我們就開創了乙個全新的議題,因為c++不允許「讓
reference
改變指向不同物件」。換個角度說,如果 p.namevalue 所指向的 string 物件被修改了,那麼就會影響到其它包含指標或引用指向此 string 物件(換句話說,此次賦值中未直接涉及到的物件),是否可以這樣做呢?這些是否是編譯器自動生成的賦值運算子應該做的呢?
面對這一問題題, c++ 拒絕編譯這類**。如果你希望為包含引用成員的類賦值,就必須手動定義賦值運算子。對於包含 const 成員的類(比如上文中修改後的 objectvalue )也一樣。修改 const 成員是非法的,所以編譯器無法在乙個隱式生成的函式中確定如何處理它們。最終,如果基類中將賦值運算子宣告為
private
的,那麼在派生類中編譯器將會把這一隱式的賦值運算子排除在外。畢竟,編譯器為派生類自動生成的賦值運算子也要處理基類中相應的部分,但如果這麼做了,這些賦值運算子不能呼叫派生類中無權呼叫的資料成員。
需要記住的:
編譯器會隱式地為乙個類生成預設建構函式、複製建構函式、賦值運算子和析構函式
條款05 了解C 默默編寫並呼叫了哪些函式
對於乙個empty class 空類 編譯器就會為它宣告 編譯器版本的 乙個copy建構函式,乙個copy assignment操作符和乙個析構函式,此外你沒有宣告任何建構函式,編譯器也會為你宣告乙個default建構函式。如下所示 當你寫下如下的 class empty 就像如下的 一樣 clas...
條款5 了解C 默默編寫並呼叫哪些函式
這些函式包括 預設建構函式,複製建構函式,賦值建構函式,以及析構函式。這些函式都屬於public部分。但是在有些情況下,賦值建構函式時沒有意義的,此時編譯器就會拒絕構造,舉乙個例子 template class test void print cout 此時,如果你定義了3個test類的物件t1,t...
條款5 了解C 默默編寫並呼叫哪些函式
1.empty class 在c 處理過後就不再是乙個empty class.編譯器會為它宣告乙個copy 建構函式,乙個copy assignment操作符,乙個析構函式。此外你如果你沒有宣告任何建構函式,編譯器也會為你宣告乙個default建構函式。所有這些函式都是public且inline。所...