乙個空類什麼時候不是空類? ---- 當c++編譯器通過它的時候。如果沒有宣告下列函式,體貼的編譯器會宣告它自己的版本。這些函式是:乙個拷貝建構函式,乙個賦值運算子,乙個析構函式,一對取址運算子。另外,如果你沒有宣告任何建構函式,它也將為你宣告乙個預設建構函式。所有這些函式都是公有的。換句話說,如果這麼寫:
class empty{};
和這麼寫是一樣的:
class empty ;
如果需要,這些函式就會被生成,但程式設計師很容易就需要它們。下面的**將使得每個函式被生成:
const empty e1; // 預設建構函式
// 析構函式
empty e2(e1); // 拷貝建構函式
e2 = e1; // 賦值運算子
empty *pe2 = &e2; // (非const)取址運算子
const empty *pe1 = &e1; // (const)取址運算子
假設編譯器為程式設計師寫了函式,這些函式又做些什麼呢?是這樣的,預設建構函式和析構函式實際上什麼也不做,它們只是能夠建立和銷毀類的物件(對編譯器來說,將一些 "幕後" 行為的**放在此處也很方便)。注意,生成的析構函式一般是非虛擬的,除非它所在的類是從乙個宣告了虛析構函式的基類繼承而來。預設取址運算子只是返回物件的位址。這些函式實際上就如同下面所定義的那樣:
inline empty::empty() {}
inline empty::~empty() {}
inline empty * empty::operator&()
inline const empty * empty::operator&() const
至於拷貝建構函式和賦值運算子,官方的規則是:預設拷貝建構函式(賦值運算子)對類的非靜態資料成員進行 "以成員為單位的" 逐一拷貝構造(賦值)。即,如果m是類c中型別為t的非靜態資料成員,並且c沒有宣告拷貝建構函式(賦值運算子),m將會通過型別t的拷貝建構函式(賦值運算子)被拷貝構造(賦值)---- 如果t有拷貝建構函式(賦值運算子)的話。如果沒有,規則遞迴應用到m的資料成員,直至找到乙個拷貝建構函式(賦值運算子)或固定型別(例如,int,double,指標,等)為止。預設情況下,固定型別的物件拷貝構造(賦值)時是從源物件到目標物件的 "逐位" 拷貝。對於從別的類繼承而來的類來說,這條規則適用於繼承層次結構中的每一層,所以,使用者自定義的建構函式和賦值運算子無論在哪一層被宣告,都會被呼叫。
看乙個namedobject模板的定義,它的例項是可以將名字和物件聯絡起來的類:
template
class namedobject ;
因為namedobject類宣告了至少乙個建構函式,編譯器將不會生成預設建構函式;但因為沒有宣告拷貝建構函式和賦值運算子,編譯器將生成這些函式(如果需要的話)。
看下面對拷貝建構函式的呼叫:
namedobjectno1("**allest prime number", 2);
namedobjectno2(no1); // 呼叫拷貝建構函式
編譯器生成的拷貝建構函式必須分別用no1.namevalue和no1.objectvalue來初始化no2.namevalue和no2.objectvalue。namevalue的型別是string,string有乙個拷貝建構函式,所以no2.namevalue初始化時將呼叫string的拷貝建構函式,引數為no1.namevalue。另一方面,namedobject::objectvalue的型別是int(因為這個模板例項中,t是int),int沒有定義拷貝建構函式,所以no2.objectvalue是通過從no1.objectvalue拷貝每乙個位元(bit)而被初始化的。
編譯器為namedobject生成的賦值運算子也以同樣的方式工作,但通常,編譯器生成的賦值運算子要想如上面所描述的那樣工作,與此相關的所有**必須合法且行為上要合理。如果這兩個條件中有乙個不成立,編譯器將拒絕為類生成operator=,就會在編譯時收到一些診斷資訊。
例如,假設namedobject象這樣定義,namevalue是乙個string的引用,objectvalue是乙個const t:
template
class namedobject ;
現在看看下面將會發生什麼:
string newdog("persephone");
string olddog("satch");
namedobjectp(newdog, 2);
namedobjects(olddog, 29);
p = s; // p中的資料成員將會發生些什麼
賦值之前,p.namevalue指向某個string物件,s.namevalue也指向乙個string,但並非同乙個。賦值會給p.namevalue帶來怎樣影響。賦值之後,p.namevalue應該指向 "被s.namevalue所指向的string" 嗎,即引用本身應該被修改嗎?如果是這樣,就很罕見了,因為c++沒有辦法讓乙個引用指向另乙個不同的物件。或者,p.namevalue所指的string物件應該被修改嗎?這樣的話,含有 "指向那個string的指標或引用" 的其它物件也會受影響,也就是說,和賦值沒有直接關係的其它物件也會受影響。這是編譯器生成的賦值運算子應該做的嗎?
面對這樣的難題,c++拒絕編譯這段**。如果想讓乙個包含引用成員的類支援賦值,就得自己定義賦值運算子。對於包含const成員的類(例如上面被修改的類中的objectvalue)來說,編譯器的處理也相似;因為修改const成員是不合法的,所以編譯器在隱式生成賦值函式時也會不知道怎麼辦。還有,如果派生類的基類將標準賦值運算子宣告為private, 編譯器也將拒絕為這個派生類生成賦值運算子。因為,編譯器為派生類生成的賦值運算子也應該處理基類部分,但這樣做的話,就得呼叫對派生類來說無權訪問的基類成員函式,這當然是不可能的。
C 異常的幕後(1)
每個人都知道良好的異常處理是困難的。在異常 生命期 的每個層面,出現這種情況的原因有許多 編寫異常安全的 是困難的,異常可能從不期望的位置丟擲 雙關語 理解設計不良的異常架構是複雜的,因為幕後發生了許多巫術,它是慢的 因為不正確地丟擲異常可能導致呼叫不可原諒的std terminate,它是危險的。...
弄清C 在幕後為你所寫 所呼叫的函式
乙個空類什麼時候不是空類?當c 編譯器通過它的時候。如果你沒有宣告下列函式,體貼的編譯器會宣告它自己的版本。這些函式是 乙個拷貝建構函式,乙個賦值運算子,乙個析構函式,一對取址運算子。另外,如果你沒有宣告任何建構函式,它也將為你宣告乙個預設建構函式。所有這些函式都是公有的。換句話說,如果你這麼寫 c...
弄清C 在幕後為你所寫 所呼叫的函式
乙個空類什麼時候不是空類?當c 編譯器通過它的時候。如果你沒有宣告下列函式,體貼的編譯器會宣告它自己的版本。這些函式是 乙個拷貝建構函式,乙個賦值運算子,乙個析構函式,一對取址運算子。另外,如果你沒有宣告任何建構函式,它也將為你宣告乙個預設建構函式。所有這些函式都是公有的。換句話說,如果你這麼寫 c...