最近剛看完Effective C++,記錄一下當前幾個比較經常使用的方法。程序員
智能指針是以對象管理資源,在構造函數中得到資源並在析構函數中釋放資源express
如下調用:安全
processWidget(std::tr1::shared_ptr<Widget>(new Widget),priority());ide
建立該調用的代碼,編譯器要作如下三件事:函數
(1)調用priority佈局
(2)執行「new Widget」spa
(3)調用tr1::shared_ptr構造函數設計
C++編譯器以什麼次序完成這些事呢?彈性很大。能夠肯定(2)必定在(3)以前,但(1)能夠排在第一或第二或第三執行。若是編譯器選擇以第二順位執行它,即(2)(1)(3)的順序,萬一對(1)的調用致使異常,此狀況下「new Widget」返回的指針將會遺失,由於它還沒有被置入tr1::shared_ptr內,而tr1::shared_ptr是咱們用來防衛資源泄漏的。因此對processWidget的調用過程可能引起資源泄漏。指針
解決辦法:使用分離語句,即分別寫出(1)建立Widget,(2)將它置入一個智能指針內,而後再把那個智能指針傳給processWidget:code
std::tr1::shared_ptr<Widget> pw(new Widget);
ProcessWidget(pw,priority()) ;
以上之因此行得通,是由於編譯器對於「跨越語句的各項操做」沒有從新排列的自由(自用在語句內它纔有那個自由度
考慮如下代碼,其中調用validateStudent,後者須要一個Student實參(by value)並返回它是否有效:
bool validateStudent(Student s);
Student plato;
Bool platoIsOK = validateStudent(plato);
上述調用會發生如下:
Student的copy構造函數會被調用,以plato爲藍本將s初始化。validateStudent返回後s會被銷燬。所以對此函數而言,參數傳遞的成本是「一次Student copy構造函數調用,加上一次Student析構函數調用」.
但那還不是所有。Student內有兩個string對象,因此每次構造一個Student對象也就構造了兩個string對象。此外,Student繼承自Person,因此每次構造Student對象也必須構造出一個Person對象。一個Person對象又有兩個string對象在其中,所以每一次Person構造動做又需承擔兩個string構造動做。
最終結果是,以by value方式傳遞一個Student對象會致使調用一次Studnt copy構造函數、兩次Person copy構造函數、四次string copy構造函數。當函數內的Student復件被銷燬,每一個構造函數調用動做都須要一個對應的析構函數動做。所以,以by value方式傳遞一個Student對象,整體成本是「6次構造函數和6次析構函數」!
如今考慮pass by reference-to-const:
bool validateStudent(const Student& s);
這種傳遞方式的效率高得多:沒有任何構造函數或析構函數被調用,由於沒有任何新對象被建立。const是很重要的,它保護了傳進去的參數不會被修改。
另外,以by reference方式傳遞參數也能夠避免slicing(對象切割)問題。當一個子類對象以by value方式傳遞並被視爲一個基類對象,基類的copy構造函數會被調用,而「形成此對象的行爲像個子類對象」的那些特化性質全被切割掉了,僅僅留下一個基類對象。
1.語法一致性
若是成員變量不是public,客戶惟一可以訪問對象的方法就是經過成員函數。客戶對class成員的訪問將統一經過成員函數來實現。
2.使用函數可使你對成員變量的處理用更精確的控制。若是你令成員變量爲pubilic,每一個人均可讀寫它,但若是你以函數取得或設定其值,你就能夠實現出「不許訪問」、「只讀訪問」、「讀寫訪問」,甚至「只寫訪問」。
3.封裝。若是你經過函數訪問變量,往後可改以某個計算替換這個成員變量,而class用戶一點也不會知道class的內部實現已經起了變化。
封裝的重要性:若是你對客戶隱藏成員變量,你能夠確保class的約束條件老是會得到維護,由於只有成員函數能夠影響他們。並且,你保留了往後變動實現的權利。若是不隱藏他們,即便擁有class源碼,改變任何public事物的能力仍是極端受到束縛,由於那會破壞太多客戶碼。Public意味不封裝,而幾乎能夠說,不封裝意味着不可改變,特別是對被普遍採用的class而言。被普遍使用的class是最須要封裝的一個族羣,由於他們最可以從「改變用一個較佳實現版本」中獲益。
假設咱們有一個public成員變量,而咱們最終取消了它。全部使用它的客戶碼都會被破壞,而那是一個不可知的大量。
protected並不更好。假設咱們有一個protected成員變量,而咱們最終取消了它。全部使用它的子類都會被破壞,而那每每也是是一個不可知的大量。
從封裝的角度來講,其實只有兩種訪問權限,private和其餘。
只要你定義了一個變量而其類型帶有一個構造函數或析構函數,那麼當程序的控制流到達這個變量定義式時,你便得承受構形成本;當這個變量離開其做用域時,你便得承受析構成本。即便這個變量最終並未被使用,仍需耗費這些成本,因此應當儘量避免這種情形。
更,不僅應該考慮延後變量的定義,直到非得使用該變量的前一刻爲止,甚至應該嘗試延後這份定義直到可以給它初值實參爲止。這樣不只可以避免構造(和析構)非必要的對象,還能夠避免無心義的default構造行爲。
以上兩種寫法的成本以下:
(1)方法A:1個構造函數+1個析構函數+n個賦值操做
(2)方法B:n個構造函數+n個析構函數
若是class的賦值成本低於一組構造+析構成本,作法A大致而言比較高效。尤爲當n值比價大時。不然B比較好。此外方法A形成名稱w的做用域比方法B大,有時那對程序的可理解性和易維護性形成衝突。所以,除非(1)你知道賦值成本比「構造+析構」成本低,(2)你正在處理代碼中效率高度敏感的部分,不然你應該使用方法B
C風格的轉型:
(T)expression //將expression轉型爲T
函數風格的轉型:
T(expression) //將expression轉型爲T
以上兩種稱爲「舊式轉型」,C++的四種新式轉型:
(1)const_cast<T>(expression)
一般用來將對象的常量性轉除。
(2)dynamic_cast<T>(expression)
主要用來執行「安全向下轉型」,也就是用來決定某對象是否屬於繼承體系中的某個類型。將一個基類對象指針(或引用)cast到繼承類指針,dynamic_cast會根據基類指針是否真正指向繼承類指針來作相應處理。是惟一可能耗費重大運行成本的轉型動做。
(3)reinterpret_cast<T>(expression)
T 必須是一個指針、引用、算術類型、函數指針或者成員指針。它能夠把一個指針轉換成一個整數,也能夠把一個整數轉換成一個指針
(4)static_cast<T>(expression)
用來強迫隱式轉換,例如將non-const對象轉爲const對象,或將int轉爲double等等。還能夠用來執行上述多種轉換的反向轉換,例如將void*指針轉爲typed指針,將point-to-base轉爲pointer-to-derived。
轉型並不是什麼都沒有作。有的時候須要內部會有一個偏移量來實現。對象的佈局方式和它們的地址計算方式隨編譯器的不一樣而不一樣,那意味着「因爲知道對象佈局」而設計的轉型,在某一平臺行得通,在其餘平臺並不必定行得通。
dynamic_cast的許多實現版本執行速度至關慢。例如至少有一個很廣泛的實現版本基於「class名稱之字符串比較」,若是你在四層深的單繼承體系內的某個對象身上執行dynamic_cast,這個實現版本每一次dynamic_cast可能會好用多達四次的strcmp調用,用以比較class名稱。深度繼承或多重繼承的成本更高。
【其實這個的內容蠻多,我只把以爲重要的兩點寫了下來】
因此,
(1)若是能夠,儘可能避免轉型,特別是在注重代碼效率的代碼中避免dynamic_cast。若是有個設計須要轉型動做,試着發展無需轉型的替代設計。
(2)若是轉型是必要的,試着將他們隱藏於某個函數背後。客戶隨後能夠調用該函數,而不需將轉型放進他們本身的代碼內。
(3)寧肯使用C++-style轉型,不要使用舊式轉型。前者很容易辨識出來,並且也比較有着分門別類的執掌。
這段代碼能夠經過編譯。但實際上它是自我矛盾的。一方面upperleft和lowerRight被聲明爲const成員函數,由於它們的目的只是爲了提供客戶一個得知Rectangle相關座標點的方法,而不是讓客戶修改Rectangle。另外一方面兩個函數卻都返回reference指向private內部數據,調用者因而經過這些reference更改內部數據。如:
Point coord1(0,0); Point coord2(100,100); const Rectangle rec(coord1,coord2); rec.upperLeft().setX(50);
這裏,upperLeft的調用者可以使用被返回的reference來改變成員。但rec其實應該是不可改變的(const)。
上面這種狀況是因爲「成員函數返回reference」。若是它們返回的是指針或迭代器,相同的狀況仍是發生,緣由也相同。reference、指針和迭代器都是所謂的handle,而返回一個「表明對象內部數據」的handle,隨之而來的即是「下降對象封裝性」的風險。
要解決以上矛盾,只要對它們返回的類型加上const便可:
即便如此,仍是可能在其餘場合帶來問題。
明確的說,他可能致使空懸的handle:這種handle所指的東西(所屬對象)不復存在。
例如某個函數返回GUI對象的外框,
例如某個函數返回GUI對象的外框,如今客戶有可能這麼使用這個函數:
對boundingBox的調用得到一個新的、暫時的Rectangle對象。這個對象沒有名稱,權且稱他temp。隨後upperLeft做用於temp身上,返回一個reference指向temp的內部成分,具體說是指向一個用一標誌temp的Point。因而pUpperLeft指向那個Point對象。目前爲止一切都還好。
可是,在哪一個語句結束以後,temp將被銷燬,而那將直接致使temp內的Point析構。最終將致使pUpperLeft指向一個再也不存在的對象。
這裏但願以D::f從新定義virtual函數B::f,但其中有個錯誤:B中的f是個const成員,而在D中它未被聲明爲const。有些編譯器可能這樣說:
warning:D::f() hides virtual B::f
有些程序員對這個信息的反應是:「噢,固然,D::f遮掩了B::f,那正是想象中該有的事!」錯,這個編譯器視圖告訴你聲明與B中名稱爲f的全部函數並未在D中被從新聲明,而是被整個遮掩掉了。
爲了讓被這樣的名稱重見天日,可以使用using聲明式或轉交函數。【這是另一條了】