有時候,某個物件是獨一無二的,不能沒複製也不能被賦值!所以我們要強制編譯器不允許使用=和copy 建構函式,但如果你不寫他們,編譯器又會自動幫你加上,問題由此引發。
class home
~uncopyable() {}
private:
uncopyable(const uncopyable&);
uncopyable& operator = (const uncopyable&);
};class home : private uncopyable ;
用這個方法可以有效地阻止home物件被賦值和複製!因為子類相應的函式必須呼叫基類相應的函式,而基類不允許它呼叫。
當基類沒有virtual析構函式時,我們把乙個基類指標指向子類指標,並且刪除基類指標時,子類的析構函式是不會起作用的。 於是,我們就把基類析構函式定為virtual來解決這個問題。
class timekeeper ;
timekeeper* ptk = gettimekeeper();
...delete ptk;
幾乎可以確定:==任何class只要帶有virtual函式都幾乎確定應該也帶有乙個virtual析構函式。==
(欲實現virtual函式,物件必須攜帶某些資訊,主要用來在執行期決定哪乙個virtual函式該被呼叫。這份資訊通常是由某乙個所謂vptr(virtual table pointer)指標所指出。vptr指向乙個由函式指標構成的陣列,稱為vtbl(virtual table)。每乙個帶有virtual函式的class都有乙個相應的vtbl。當物件呼叫某一virtual函式,實際被呼叫的函式取決於物件的vptr所指的那個vtbl–編譯器在其中尋找適當的函式指標。)
class specialstring : public string ;
specialstring* pss = new specialstring();
string* ps;
ps = pss;
...delete ps;
// 沒有定義!ps的specialstring的資源會洩露!
相同的分析使用於任何不帶virtual析構函式的class,包括stl容器如vector,list, set, map…
c++並不禁止析構函式吐出異常,但它不鼓勵你這麼做。因為如果析構過程中吐出異常,析構過程就會結束導致不明確的行為或者記憶體洩露。
請記住:
在基類中呼叫乙個virtual函式,會出現意外情況!原因就在於在base class構造完成前,derived class還沒有被構造。
class transaction ;
transaction::transaction()
class buytransaction : public transaction ;
// now!
buytransaction b; // error occurs!!!
問題出現了!buytransaction的物件b要先構造transaction,但在此期間還呼叫了logtransaction,因為此時子類還沒有被構造,所以編譯器只能呼叫基類的logtransaction,又因為基類中logtransaction是乙個純虛函式,無法被呼叫,於是出現的編譯錯誤。而且還基類的transaction()函式中,logtransaction會被認為是基類的呼叫,毫無疑問會出問題!
一種更好的方法是,不使用虛函式!
class transaction
void logtransaction(const string& info) const;
...};class buytransaction : public transaction
...private:
static string create(params);
};
在構造期間,你可以通過」令derived class將必要的構造資訊向上傳遞給base class建構函式「替換而彌補!。
值得注意的是,static string create(params)。比起成員初值列內給予base class所需資料,利用輔助函式建立乙個值傳給base class建構函式往往比較方便而可讀。令此函式為static,也就不可能以外指向」初期未成熟之buytransaction物件內尚未初始化的成員變數「。這很重要,正式因為」那些成員變數處於未定義狀態「,所以」在base class構造和析構期間呼叫virtual函式不可下降至derived class「。
為了實現「連鎖賦值」,賦值操作符必須返回乙個reference指向操作符左側實參。這是你為class實現賦值操作符時應該遵循的協議。
不僅如此,所有賦值相關運算都應該符合這個協議!
class widget
...
賦值操作總是要先clear,於是如果自我賦值,*this 和 引用的orig都會被clear掉,這樣*this就會失去資料。出現自我賦值是因為別名的存在:所謂別名就是「有乙個以上的方法指向某物件」。一般而言如果某段**操作pointer或reference而他們被用來指向多個相同型別的物件,就需要考慮這些物件是否為同乙個。實際上,兩個物件只要來自同乙個繼承體系,它們甚至不需要宣告為相同型別就可能造成別名,因為乙個base class的reference或pointer可以指向乙個derived class物件。
widget& widget::operator=(const widget& orig)
delete pb;
pb = new bitmap(*rhs.pb);
return *this;
}
此方法具有「自我賦值安全性」,但不具備「異常安全性」。
如果「new bitmap」導致遺產(可能因為分配時記憶體不足或因為bitmap的copy建構函式丟擲異常),widget最終會持有乙個指標指向一塊被刪除的bitmap。
* 只需要注意在賦值pointer所指東西之前別刪除pointer。
widget& widget::operator=(const widget& orig)
現在,如果new過程中丟擲異常,pb仍然保持原樣。雖然這不是自我賦值的最高效解決方法,但卻可行。
當然也可以在這個函式前加上證同測試。
widget& widget::operator=(const widget& orig)
設計良好的物件導向系統,會將物件的內部封裝起來,只留兩個函式負責物件拷貝,那是帶著適切名稱的copy建構函式和copy assignment操作符,我稱它們為copying函式。如果你宣告自己的copying函式,意思就是告訴編譯器你並不喜歡預設實現中的某些行為。但後果就是,當你的實現**幾乎必然出錯時,編譯器不會告訴你。例如,copying函式執行了區域性拷貝(有的變數沒有被賦值)
當你編寫乙個copying函式,請確保:1)複製所有local成員變數,2)呼叫所有base class內適當的copying函式。
令copy assignment操作符呼叫copy建構函式是不合理的,因為這就像試圖構造乙個已經存在的物件!
同樣的,令copy構造函式呼叫copy assignment操作符也是無意義的!建構函式用來初始化新物件,而assignment操作符只施行於已初始化物件上。對乙個尚未構造好的物件賦值,就像在乙個尚未初始化的物件身上做「只對初始化物件才有意義」的事一樣。
如果copy建構函式和copy assignment操作符**相似,可以重新寫乙個函式命名為init,並設為private。
c 構造 析構 賦值 運算
1 為多型基類宣告virtual析構函式 帶有多型形態的base classs應該宣告乙個virtual析構函式。如果該class帶有任何virtual的函式,它就應該擁有乙個virtual析構函式。這樣用基類指標指向的派生類的析構的時候,才會呼叫到自己的析構函式,將派生類的所有部分都析構掉,否則只...
構造 析構 賦值運算
非內建資料型別 一般而言,只有當生出的 合法且有適當機會證明它有意義,編譯器才會為class 生出operator 建構函式 析構函式 stl 或標準庫或已經存在的,不包含虛函式的類,我們不應該繼承它們 比較好的一種辦法是,自己在析構函式中,可以選擇,記錄並退出,或者記錄並繼續執行。但同時提供乙個p...
構造 析構 賦值運算
條款05 了解c 默默編寫並呼叫哪些函式 如果我們寫了乙個空類 class empty 編譯器會為這個類新增一些default的函式,相當於 class empty default建構函式 empty const empty rhs copy建構函式 empty 析構函式 empty operato...