條款01:視c++為乙個語言聯邦
條款02:盡量以const,enum,inline替換#define
條款03:盡可能使用const
條款04:確定物件被使用前已先被初始化
條款05:了解c++默默編寫並呼叫哪些函式
什麼時候empty class不再是個empty class呢?當c++處理過它之後.是的,如果你自己沒有宣告,編譯器就會為它宣告乙個copy建構函式,乙個copy assignment操作符和乙個析構函式.此外如果你沒有宣告任何建構函式,編譯器也會為你宣告乙個default建構函式.所有這些函式都是public且inline.因此,如果你寫下:
class empty{};
這就好像你寫下這樣的**:
class empty
empty(const empty & rhs)
~empty()
empty & operator=(const empty & rhs)
};惟有當這些函式被需要,它們才會被編譯器建立出來.程式中需要它們是很平常的事.下面**造成上述每乙個函式被編譯器產生:
empty el;
//default建構函式
empty e2(e1);
//copy建構函式
e2 = e1;
//copy assignment操作符
條款06:若不想使用編譯器自動生成的函式,就該明確拒絕
class homeforsale;
讓homeforsale物件拷貝動作以失敗收場:
homeforsale h1;
homeforsale h2;
homeforsale h3(h1);
//企圖拷貝h1--不該通過編譯
h1 = h2;
//企圖拷貝h2--也不該通過編譯
通常如果你不希望class支援某一特定機能,只要不宣告對應函式就是了.但這個策略對copy建構函式和copy assignment操作符不起作用,條款05已經指出,如果你不宣告它們,而某些人嘗試呼叫它們,編譯器會為你宣告它們.
如果你不宣告copy建構函式或copy assignment操作符,編譯器為你產出乙份,如果你宣告它們,你的class還是支援copying.但這裡的目標卻是要阻止copying.
答案的關鍵是,所有編譯器產出的函式都是public.為阻止這些函式被建立出來,你得自行宣告它們,但這裡並沒有什麼需求使你必須將它們宣告為public.因此你可以將copy建構函式或copy assignment操作符宣告為private.藉由明確宣告乙個成員函式,你阻止了編譯器暗自建立其專屬版本;而令這些函式為private,使你得以成功阻止人們呼叫它.
一般而言這個做法並不絕對安全,因為member函式和friend函式還是可以呼叫你的private函式.除非你夠聰明,不去定義它們,那麼如果某些人不慎呼叫任何乙個,會獲得乙個連線錯誤."將成員函式宣告為private而且故意不實現它們"這一伎倆是如此為大家接受,因而被用在c++ iostream程式庫中阻止copying行為.將這個伎倆施行於homeforsale也很簡單:
class homeforsale;
有了上述class定義,當客戶企圖拷貝homeforsale物件,編譯器會阻撓他.如果你不慎在member函式或friend函式之內那麼做,輪到聯結器發出抱怨.
將連線期錯誤移到編譯期是可能的(而且那是好事,畢竟愈早偵測出錯誤愈好),只要將copy建構函式和copy assignamet操作符宣告為private就可以辦到,但不是在homeforsale自身,而是在乙個專門為了阻止copy動作而設計的base class內:
class uncopyable
~uncopyable(){}
private:
uncopyable(const uncopyable &);
uncopyable & coperator=(const uncopyable &);
};class homeforsale:private uncopyable;
現在只要任何人(甚至是member函式或friend函式)嘗試拷貝homeforsale物件,編譯器便試著生成乙個copy建構函式和乙個copy assignment操作符,而正如條款12所說,這些函式的"編譯器生成版"會嘗試呼叫其base class的對應兄弟,那些呼叫會被編譯器拒絕,因為其base class的拷貝函式是private.
條款07:為多型基類宣告virtual析構函式
class timekeeper;
class atomickclock:public timekeeper;
//原子鐘
class waterclock:public timekeeper;
//水鐘
class wristwatch:public timekeeper;
//腕表
設計factory函式,返回指標指向乙個計時物件:
timekeeper * gettimekeeper();
為遵守factory函式的規矩,被gettimekeeper()返回的物件必須位於heap.因此為了避免洩漏記憶體和其他資源,將factory函式返回的每乙個物件適當地delete掉很重要:
timekeeper * ptk = gettimekeeper();
...delete ptk;
問題來了,如果gettimekeeper返回的指標指向乙個derived class物件,而那個物件卻經由乙個base class指標被刪除,而目前的base class(timekeeper)有個non-virtual析構函式.這時很可能derived class成分沒被銷毀,其析構函式也未能執行,而base class成分已被銷毀,於是造成乙個詭異的"區域性銷毀"物件.
消除這個問題的做法很簡單:給base class乙個virtual析構函式.
class timekeeper;
timekeeper * ptk = gettimekeeper();
...delete ptk;
//現在,行為正確
如果class不含virtual函式,通常表示它並不意圖被用作乙個base class.當class不企圖被當作base class,令其析構函式為virtual往往是個餿主意.
條款08:別讓異常逃離析構函式
c++並不禁止析構函式吐出異常,但它不鼓勵你這樣做.考慮以下**:
class widget
//假設這個可能吐出乙個異常
};void dosomething()
//v在這裡被自動銷毀
當vector v被銷毀,它有責任銷毀其內含的所有的widgets.假設v內含十個widgets,而在析構函式第乙個元素期間,有個異常被丟擲.其他九個widgets還是應該被銷毀,因此v應該呼叫它們各個析構函式.但假設在那些呼叫期間,第二個widget析構函式又丟擲異常.現在有兩個同時作用的異常,這對c++而言太多了.在兩個異常同時存在的情況下,程式若不是結束執行就是導致不明確行為.
舉個例子,假設你使用乙個class負責資料庫連線:
class dbconnection;
為確保客戶不忘記在dbconnection物件身上呼叫close(),乙個合理的想法是建立乙個用來管理dbconnection資源的class,並在其析構函式中呼叫close.
class dbconn
private:
dbconnection db;
};這便允許客戶寫出這樣的**:
//開啟乙個區塊(block)
//建立dbconnection物件並交給dbconn物件以便管理
//通過dbconn介面使用dbconnection物件
//在區塊結束點,dbconn物件被銷毀,因而自動為dbconnection物件呼叫close
只要呼叫close成功,一切者美好.但如果該呼叫導致異常,dbconn析構函式會傳播該異常,也就是允許它離開這個析構函式.
兩個辦法可以避免這一問題.dbconn的析構函式可以:
1 如果close丟擲異常就結束程式
dbconn::~dbconn()catch(...)
}2 吞下因呼叫close而發生的異常
dbconn::~dbconn()catch(...)
}條款09:絕不在構造和析構過程中呼叫virtual函式
class transaction;
transaction::transaction()
class buytransaction:public transaction;
class selltransaction:public transaction;
當以下行被執行時,會發生什麼事:
buytransaction b;
無疑地會有乙個buytransaction建構函式被呼叫,但首先transaction建構函式一定會更早被呼叫.是的,derived class物件內的base class成分會在derived class自身成分被構造之前先構造妥當.transaction建構函式最後一行呼叫virtual函式logtransaction,這時被呼叫的logtransaction是transaction內的版本,不是buytransaction內的版本--即使目前即將建立的物件型別是buytransaction.
要解決這個問題,一種做法是在class transaction內將logtransaction函式改為non-virtual,然後要求derived class建構函式傳遞必要資訊給transation建構函式:
class transaction;
transaction::transaction(const std::string & loginfo)
class buytransaction:public transaction
...private:
static std::string createlogstring(parameters);
};換句話說由於你無法使用virtual函式從base classes向下呼叫,在構造期間,你可以藉由"令derived classes將必要的構造資訊向上傳遞至base class建構函式"替換之而加以彌補.
讀書筆記 1
從我第一次看到windows就對它那花花綠綠的外表所吸引,大學兩年過來,時間又讓我從另乙個角度重新認識的了這些美麗的。本學期開始圖形程式設計的學習,探索windows圖形系統,並對gdi api,directdraw api進行學習。之所以寫部落格,第 一 是想勉勵自己不斷學習,讓大家監督 第 二 ...
讀書筆記1
netstat p525 網路資訊服務是通過本地查詢,還是要連線到遠端資料庫 p527 網路配置檔案?服務資訊函式?linux下如何組網 p527 如何設定計算機的主機名 p528 套接字 第15章 套接字 套接字 p513,523 what 套接字 一種程序間通訊機制 不僅可以本地程序通訊,也可以...
讀書筆記1
盡量以const,enum,inline,替換 define 我們無法使用 define建立乙個class專屬常量。因為 define不注重作用域。也不能提供任何的封裝性,也就是說沒有所謂的private define這樣的東西。而const成員是可以被封裝的。乙個const的位址是合法的,但取乙個...