四 設計與宣告

2022-07-09 09:00:16 字數 4276 閱讀 8834

本章將對良好c++介面的設計和宣告作出闡述,對某些最頻繁出現的錯誤提出警告,為class、function和template設計經常遇見的問題作出解答。

請記住

1.好的介面很容易被正確使用,不容易被誤用。

2.促進正確使用的辦法包括介面的一致性,以及與內建型別的行為相容。

3.阻止誤用的辦法包括建立新型別、限制型別的操作、束縛物件值、以及消除客戶的資源管理責任。

4.tr1::shared_ptr支援定製刪除器,這可防範dll問題,可被用來自動解除互斥鎖(見條款14)等等。

設計class是一項艱鉅的工作,因為設計好的type很難。好的type有自然的語法,直觀的語義,以及乙個或多個高效實現品。那麼如何設計乙個高效的classes?

新type的物件如何被建立和銷毀?這會影響到你的class的建構函式和析構函式以及記憶體分配和釋放函式

物件的初始化和物件的賦值該有什麼樣的差別?這會決定你的建構函式和賦值操作符的行為,以及其間的差異,別混淆了「初始化」和「賦值」,這對應不同的函式呼叫。

新type的物件如果被passed by value,意味著什麼?copy建構函式用來定義乙個type的pass-by-value該如何實現。

什麼是新type的「合法值」請妥善考慮你的數值定義集合。

新type需要維護配合某些繼承關係嗎?這會導致你是否受到另乙個類的virtual或non-virtual函式的影響。

新type需要什麼樣的轉換?請留心顯示轉換和隱式轉換。

什麼樣的操作符和函式對新type是合理的這個問題的答案決定你會為你的class宣告哪些函式。其中哪些該是成員函式。

什麼樣的標準應該駁回拒絕?這會讓你思考決定另哪乙個成員為public,哪個為protecetd,哪個為private,還會幫助你思考友元函式的定位和設計。

新type有多麼一般化?追求極致一般化,應該考慮使用模板

真的需要乙個新type嗎?如非必要,勿增實體。

什麼是新type的未宣告介面?

請留意本節涵蓋的class設計主題。

pass-by-value方式會帶來高額的物件構造成本和析構成本。此外,還會在繼承類中造成「切割」現象,當乙個dericed class以pass-by-value方式傳遞並被視為乙個base class物件時,其derived class的特質化性質會被切割掉。

採用pass-by-reference方式應新增關鍵字const保證原有物件不被修改。

在c++編譯器的底層,reference往往以指標實現出來,因此pass-by-reference通常意味真正傳遞的是指標。

reference代表著乙個物件的另乙個名稱,應該確保能找到這個物件的本名,才可使用reference.

請記住

絕不要返回pointer或reference指向乙個local stack物件,或返回reference指向乙個heap-allocated物件,或返回pointer和reference指向乙個local static物件而有可能同時需要多個這樣的物件。條款4已經為「在單執行緒環境中合理返回reference指向乙個local static物件」提供了乙份設計例項。

如果對客戶隱藏成員變數(即封裝成員變數和方法),可以確保class的約束條件總是會得到維護,因為只有成員函式才可以影響它們。如果不隱藏,那麼可以說不封裝意味著不可改變,因為修改public的原始碼是非常複雜的工作,是不可知的大工作量。protected也一樣,可以認為protected不具備封裝性質,它和public都是不可維護的。其實只有兩種訪問許可權,private提供封裝和不提供封裝。

請記住

1.將成員變數宣告為private,這可賦予客戶訪問資料的一致性、可細微劃分訪問控制、允諾約束條件得到保證,並可提供充分的實現彈性。

2.protected並不比public更具封裝性。

越少**能看到資料,則越多的資料可以被封裝。

將所有便利函式放在多個標頭檔案內但隸屬於同乙個命名空間,意味客戶可以輕鬆擴充套件這一組遍歷函式,只需要新增更多的non-mmeber和non-friend函式到此命名空間中去。新函式就像其他舊有的遍歷函式可用且集成為一體,這是class無法提供的另乙個性質,因為class定義是對客戶是不可擴充套件的。因為class不能分割為片段,必須整體定義。

請記住

寧可拿non-member non-friend函式替換member函式,這樣做可增加封裝性,包裹彈性和機能擴充性。

只有當引數被列於引數列內,這個引數才是隱式型別轉換的合格參與者。地位相當於「被呼叫之成員函式所隸屬的那個物件」,如下:

class rational;

rational onehalf(1,2);

result = onehalf * 2; //編譯通過

result = 2 * onehalf; //編譯錯誤

//以函式形式重寫:

result = onehalf.operator*(2); //正確

result = 2.operator*(onehalf); //錯誤

正確宣告為:

class rational;

const rational operator*(const rational& lhs, const rational& rhs) //現在是乙個non-member函式

rational onefourth(1, 4);

rational result;

result = onefourth * 2;

result = 2 * onefourth; //通過編譯

請記住

如果你需要為某個函式的所有引數進行型別轉換,請使用non-member函式

swap的典型實現:

namespace std

}

這個典型對某些型別實現效率不高,比如以pimpl手法設計的class(pimpl是pointer to implemtation)widgetimpl為例:

class widgetimpl;

class widget

...private:

widgetimpl* pimpl; //指標,所指物件含widget資料

}

令widget宣告乙個名為swap的public成員函式做真正的置換工作,然後將std::swap特化,令它呼叫成員函式:

class widget

...};

namespace std

}

接下來,再此基礎上進行優化,讓使用者呼叫swap時取得較高效的template版本。首先宣告乙個non-member swap函式呼叫 member swap函式,將所有widget的所有相關機能都被置於命名空間widgetstuff內,整個結果看起來便是這樣。

namespace widgetstuff;

...templatevoid swap(widget& a, widget& b)

}

以上,已經討論了default swap,member swap, non-member swap和std::swap特化版本以及對swap函式的呼叫。

請記住

1.當std::swap對某些型別效率不高時,提供乙個swap成員函式,並保證該函式不丟擲異常。

2.如果提供乙個member swap,則也應該提供乙個non-member swap用來呼叫前者,對於classes還應該特殊化std::swap;

3.呼叫swap時應針對std::swap使用using 宣告式,然後直接呼叫swap

4.為「使用者定義型別」進行std template全特化是好的,但不能夠在std內加入新東西。

設計與宣告

預設情況下c 是以值傳遞的形式傳遞物件到函式的。除非特別指定,否則函式引數都是以實際實參的副本為初值。呼叫端所獲的也是函式返回值的乙個副本。這些副本是由物件的拷貝建構函式產生,這會使值傳遞非常費時。特別是在乙個結構複雜的類中,例如 class student public person當有以下函式 ...

設計與宣告

條款18 讓介面容易被正確使用,不易被誤用 條款19 設計class猶如設計type 條款20 寧以pass by value to const替換pass by value 預設情況下c 以by value方法給物件傳遞引數,函式引數都是物件的副本,這些副本是由物件的copy建構函式產出,這可能使...

設計與宣告(一)

條款18 讓介面容易被正確使用,不易被誤用 開發乙個 容易被正確使用,不易被誤用 的介面,首先必須考慮使用者會做出什麼樣的錯誤。以下為例 class date 乍見之下這個介面通情達理,但是至少容易犯兩個錯誤。第一,他們可能以錯誤的次序傳遞引數 第二,他們可能傳遞乙個無效的引數。許多客戶端錯誤可以因...