C 構造 析構 賦值運算

2021-07-11 21:04:05 字數 3896 閱讀 9780

有時候,某個物件是獨一無二的,不能沒複製也不能被賦值!所以我們要強制編譯器不允許使用=和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...