item 23:
用非成員非友元函式取代成員函式
想象乙個象徵
web
urls
的歷史,以及從系統移除所有
cookies
的功能:
class webbrowser ;
很多使用者希望能一起執行全部這些動作,所以
webbrowser
可能也會提供乙個函式去這樣做:
class webbrowser ;
當然,這個功能也能通過非成員函式呼叫適當的成員函式來提供:
void clearbrowser(webbrowser& wb)
那麼哪個更好呢,成員函式
cleareverything
還是非成員函式
clearbrowser?
從封裝性來說,因為
clearbrowser
不能訪問類的任何私有成員,因此
clearbrowser
(非成員非友元函式)比
cleareverything
(成員函式)更可取:它能為
webbrowser
獲得更強的封裝性。
可以採用以下的方式來定義:
namespace webbrowserstuff ;
void clearbrowser(webbrowser& wb);
...}
將clearbrowser
和class webbrowser
放在同乙個名字空間內。
乙個類似
webbrowser
的類可以有大量的方便性函式,一些是書籤相關的,另一些列印相關的,還有一些是
cookie
管理相關的,等等。作為乙個一般的慣例,多數客戶僅對這些方便性函式的集合中的一些感興趣。沒有理由讓乙個只對書籤相關的方便性函式感興趣的客戶在編譯時依賴其它函式,例如,
cookie
相關的方便性函式。分隔它們的直截了當的方法就是在乙個標頭檔案中宣告書籤相關的方便性函式,在另乙個不同的標頭檔案中宣告
cookie
相關的方便性函式,在第三個標頭檔案宣告列印相關的方便性函式,等等:
//header "webbrowser.h"- header for class webbrowser itself
// as well as "core" webbrowser-related functionality
namespace webbrowserstuff ;
... // "core" related functionality, e.g.
// non-member functions almost
// all clients need
}//header "webbrowserbookmarks.h"
namespace webbrowserstuff // functions
//header "webbrowsercookies.h"
namespace webbrowserstuff // functions
將所有方便性函式放入多個標頭檔案中
——但是在乙個
namespace中——
也意味著客戶能容易地擴充方便性函式的集合。他們必須做的全部就是在
namespace
中加入更多的非成員非友元函式。
總結:
用非成員非友元函式取代成員函式。這樣做可以提高封裝性,包裹彈性,和機能擴充性。
item 24:
當所有引數皆需型別轉換,請為此採用非成員函式
設計乙個用來表現有理數的類
class rational ;
研究一下讓
operator*
成為rational
的乙個成員函式的想法究竟如何:
class rational ;
這個設計讓你在有理數相乘時不費吹灰之力:
rational oneeighth(1, 8);
rational onehalf(1, 2);
rational result = onehalf * oneeighth; // fine
result = result * oneeighth; // fine
當你試圖做混合模式的算術運算時,可是,你發現只有一半時間它能工作:
result = onehalf * 2; // fine
result = 2 * onehalf; // error!
當你重寫最後兩個例子為功能等價的另一種形式時,問題的**就變得很明顯了:
result = onehalf.operator*(2); // fine
result = 2.operator*(onehalf); // error! 物件
onehalf
是乙個包含
operator*
的類的例項,所以編譯器呼叫那個函式。然而,整數
2 與類沒有關係,因而沒有
operator*
成員函式。編譯器同樣要尋找能如下呼叫的非成員的
operator*
s(也就是說,在
namespace
或全域性範圍內的
operator*s):
result = operator*(2, onehalf); // error!
但是在本例中,沒有非成員的持有乙個
int和乙個
rational
的operator*
,所以搜尋失敗。
當然,編譯器這樣做僅僅是因為提供了乙個非顯性的建構函式。如果
rational
的建構函式是顯性的,這些語句都將無法編譯:
result = onehalf * 2; // error! (with explicit ctor);
// can't convert 2 to rational
result = 2 * onehalf; // same error, same problem
支援混合模式操作失敗了,但是至少兩個語句的行為將步調一致。
然而,你的目標是既保持一致性又要支援混合運算,也就是說,乙個能使上面兩個語句都可以編譯的設計。讓我們返回這兩個語句看一看,為什麼即使
rational
的建構函式不是顯式的,也是乙個可以編譯而另乙個不行:
result = onehalf * 2; // fine (with non-explicit ctor)
result = 2 * onehalf; // error! (even with non-explicit ctor)
其原因在於僅僅當引數列在引數列表中的時候,它們才有資格進行隱式型別轉換。而對應於成員函式被呼叫的那個物件的隱含引數
—— this
指標指向的那個
——根本沒有資格進行隱式轉換。這就是為什麼第乙個呼叫能編譯而第二個不能。第一種情況包括乙個引數被列在引數列表中,而第二種情況沒有。
你還是希望支援混合運算,然而,現在做到這一點的方法或許很清楚了:讓
operator*
作為非成員函式,因此就允許便一起將隱式型別轉換應用於所有引數:
class rational ;
const rational operator*(const rational& lhs, // now a non-member
const rational& rhs) // function
rational onefourth(1, 4);
rational result;
result = onefourth * 2; // fine
result = 2 * onefourth; // hooray, it works!
另外,operator*
完全能夠根據
rational
的public
介面完全實現,因此無需將其宣告為友元函式。上面的**展示了做這件事的方法之一。這匯出了一條重要的結論:與成員函式相對的是非成員函式,而不是友元函式。
總結:
如果你需要在乙個函式的所有引數(包括被
this
指標所指向的那個)上使用型別轉換,這個函式必須是乙個非成員函式。
Effective C 筆記 4 設計與宣告
條款18 讓介面容易被正確使用,不易被誤用 請記住 好的介面很容易被正確使用,不容易被誤用。你應該在你的所有介面中努力達到這些性質。促進正確使用 的方法包括介面的一致性,以及與內建型別的行為相容。阻止誤用 的辦法包括建立新型別 限制型別上的操作,束縛物件值,以及消除客戶的資源管理責任。trl sha...
《Effective C 》讀書筆記 設計與宣告
在c 介面設計中,乙個基本的原則是 讓介面容易被正確使用,不容易被誤用。接下來的所有討論都基於這一基本原則。寧以引用方式 pass by reference to const 替換傳值方式 pass by value 當使用傳值方式時,實際上傳遞的是物件的乙個副本,這樣會呼叫其建構函式和析構函式,增...
Effective C 筆記 物件導向設計
摘自 effetive c 中文版第三版 從編譯器來看,符號表與編譯的各個階段都有互動,一般來講,符號表有記憶體位址和函式 變數的對應關係,編譯時節點的各種屬性 型別,作用域,分配空間大小,函式 的引數型別等 因為例如 define kkk 1.653 段在編譯器開始處理原始碼之前可能會被預處理器取...