五 實現 條款28 30

2022-08-16 02:36:12 字數 4164 閱讀 6215

通常我們說的內部成分就是它的成員變數或者非public的成員函式。

書上的例子很好的解釋了為什麼不返回物件內部成分。

首先有乙個座標類,

class point

;

然後有乙個矩形資料類:包含矩形的左上角和右下角座標。

struct rectdata

;

最後看我們的矩形類:

class rectangle

point &lowerright() const

private:

shared_ptrpdata;

};

當我們執行以下**時:

point p1(0,0);

point p2(100,100);

const rectangle rec(p1,p2);

// 修改左上角座標

rec.upperleft.setx(20);

經過這樣的**後,雖然我們使用const修飾了rec變數,但是我們還是可以修改rec內部的資料!!

這就和bitwise帶來的額外效果一樣,如果我們返回了乙個引用,在函式中我們並沒有改變任何資料,但是實際上我們通過其返回的引用改變了成員變數!這讓我們的const功虧一簣。

解決方法

只需要在rectangle中的函式新增乙個const即可:

const point &upperleft() const;

const point &lowerright() const;

這樣返回的也是乙個常量引用,不能修改乙個常量了。

即這種handles所指東西將不復存在。最常見的就是乙個函式返回值:

假設有乙個gui物件外框類:

class guiobject

;const rectangle boundingbox(const guiobject& obj);

考慮以下**呼叫會發生什麼:

guiobject *pgo;

const point *pupleft = &(boundingbox(pgo).upperleft);

事實上這一句非常危險!boundingbox函式返回乙個rectangle的臨時物件,當語句結束後,這個臨時物件就會被析構,不復存在。而pupleft則指向乙個已經被銷毀的物件。所以會變成空懸、虛吊(dangling)的情況!

特別注意:無論何時,返回乙個臨時物件不要寫成一句複雜的語句,分成幾句去寫,我們如果用變數來接受這個臨時物件,就不會產生一些語句結束後這個臨時物件馬上就被銷毀,從而造成虛吊的情況。

避免返回handles(包括reference,指標,迭代器)指向物件內部。遵守這個條款可增加封裝性,幫助const成員函式的行為像個const,並將發生「dangling handles」的可能性降至最低。

本條款一開始所用的例子其實之前也有講過幾次,比如在處理自我賦值的時候就講過。本質是一樣的。在此條款中只是又舉了乙個例子:切換背景影象的例子。

class prettymenu

;void prettymenu::changebackground(std::istream *imgsrc)

為了防止new丟擲異常後導致鎖一直被持有,或者是次數已經增加了但是後面執行卻由於new丟擲的異常而失敗了,

我們還是利用條款14中的乙個資源管理類lock。這樣做的好處:

(1)基本承諾。如果異常被丟擲,程式的任何事物仍然保持在有效的狀態下,沒有任何物件或者資料結構因此被破壞。比如上述的增加的次數不能沒成功就加1,又或者鎖被一直持有。

(2)強烈保證。如果異常被丟擲,程式狀態不改變。如果函式成功,就是完全成功,如果失敗,那就是完全恢復到呼叫之前的狀態。不能夠 部分成功。

(3)不拋擲保證。承諾絕不丟擲異常。作用於內建型別的身上的所有操作都有nothrow保證。一般有nothrow的方法可以這麼寫:

int dosomething() throw();
這只是表明dosomething丟擲異常的話會造成致命錯誤!並不是說這個函式決定不會丟擲異常!

copy-and-swap策略可以幫我們進行「強烈保證」——要麼成功,要麼恢復到之前的狀態,絕不存在部分成功的狀態。

原則(1) 為你打算修改的物件做出乙份副本,在副本上進行一切必要的修改。

(2) 如果修改過程中丟擲了異常,原物件還是保持不變,只是副本變了。

(3) 如果所有修改都成功了,就將副本和原物件在乙個不會丟擲異常的函式中進行交換。

使用copy-and-swap修改上述改變背景影象版本

問題的關鍵就是有乙份副本,我們需要的副本就是實際需要改變的東西:背景顏色和修改次數。所以我們可以寫乙個struct包含這兩個成員。

此外,為了swap的高效,條款25中已經提到,我們如果只是交換指標所指則會更高效。所以我們在原來的prettymenu中提供乙個智慧型指標。

如下:

struct pmimpl

class prettymenu

;void prettymenu::changebackground(std::istream *imgsrc)

上述**就實現了copy-and-swap策略。有乙個要注意的細節:我們將pnew定義為shared_ptr型別的,這樣出了這個作用域,它就自動析構了。

這份**還有乙個可能丟擲異常的點在於image的拷貝建構函式。

而當「強烈保證」不切實際時,就應該提供「基本承諾」。

原理可被拒絕

而且,inline關鍵字只是乙個對編譯器的申請,而不是強制命令。如果編譯器覺得函式太複雜,就會忽略inline關鍵字。比如函式中帶有迴圈或者遞迴功能。

是編譯期的行為

為了將函式呼叫替換為展開為呼叫函式體內的本體,編譯器必須知道函式的本體是怎麼樣的,所以大多數c++的inline都是處於編譯期完成的(也有一些是在執行期!)。

接下來我們看看此條款下有對inline函式的建議。

呼叫上效率的提公升不代表別的方面沒有損失:

很明顯是不能達到目的的。

上面我們已經說明了,編譯器為了知道函式本體長什麼樣子,所以inline在大多數編譯器上都是編譯期行為。

但是virtual函式是執行期決定呼叫哪個函式的。所以對乙個virtual函式進行inline,編譯器肯定會拒絕此inline。

virtual函式,上述已經講過,不再累述。

編譯器通常不對「通過函式指標而進行的呼叫」實施inlining。

inline void f() // 編譯器有意願進行inline

void (*pf)() = f; // 函式指標

...f(); // 實施內聯

pf(); // 拒絕內聯

這個地方書上沒有細講,在我看來的原因是pf只是乙個指向乙個無引數的函式指標,它可以指向f(),也可以指向其它無引數的函式,編譯器不能判斷它的函式本體是否適合inlining.

建構函式和析構函式。讓我們假設乙個drive類,它的建構函式和析構函式都是空的函式體,不含任何**。看起來是成為inlining的絕佳候選人,但是別忘了考慮繼承的情況:倘若drive類繼承自base類,那麼在drive構造之前就會構造,如果base類很複雜呢?

將大多數inlining限制在小型、被頻繁呼叫的函式身上。這可使日後的除錯過程和二進位制公升級更容易,也可使潛在的**膨脹問題最小化,使程式的速度提公升機會最大化。

不要只因為function templates出現在標頭檔案,就將它們宣告為inline.

C 實現 26 31條款

std string str str password 將password作為encrypted的初值,跳過毫無意義的default構造過程 std string str password 上述行為可以避免構造非必要物件,還可以避免無意義的default構造行為。c風格的轉型 t expressio...

條款34 區分介面繼承和實現繼承

作為乙個 class 的設計者 1 有的時候你想要 derived classes 只繼承乙個 member function 的 inte ce declaration 宣告乙個 pure virtual function的目的是使 derived classes 繼承乙個函式 inte ce o...

php mvc實現步驟五

依據 功能的相關性,將一系列相關的功能,使用乙個控制器類來處理,而該控制器的每個方法,就對因 某個功能。注意 控制器是按照功能劃分的 而不是像模型一樣,按表來劃分 比賽 以上的listaction 操作應該如何被呼叫呢 例項化 並掉用 方法即可 在 例項化或呼叫呢?增加 乙個可以例項化並呼叫控制器方...