條款18 讓介面容易被正確使用,不易被誤用
總結:
1、好的介面很容易被正確使用,不容易被誤用。你應該在你的所有介面中努力達成這些性質。
2、促進正確使用的方法包括介面的一致性,以及與內建型別的行為相容。
3、預防錯誤的方法包括建立新的型別,限定型別的操作,約束物件的值,以及消除客戶的資源管理職責。
4、tr1::shared_ptr 支援自定義 deleter。這可以防止 cross-DLL 問題,能用於自動解鎖互斥體(mutex)等。
C++ 被淹沒於介面中。函式介面、類介面、模板介面。每一個介面都是客戶與你的程式碼互動的手段。在理想情況下,如果使用某個介面而沒有得到預期的行為,這個程式碼不該編譯通過,反過來,如果程式碼可以編譯,那麼它做的就是客戶想要的。
開發易於正確使用,而難以錯誤使用的介面需要你考慮客戶可能造成的各種錯誤。例如,假設你正在設計一個用來表現日期的類的建構函式:
class Date {
public:
Date(int month, int day,int year);
...
};
客戶可能很容易地造成以錯誤順序傳遞引數或傳遞非法日期的錯誤:
Date d(30, 3, 1995); // Oops! Should be"3, 30" , not "30, 3"
Date d(2, 20, 1995); // Oops! Should be"3, 30" , not "2, 20"
很多客戶錯誤都可以通過引入新型別來預防。確實,型別系統是你阻止那些不合適的程式碼通過編譯的主要支持者。我們可以引入簡單的外覆型別來區別日,月和年,並將這些型別用於 Data 的建構函式。
struct Day { //Month和Year與之類似
explicit Day(int d) :val(d) {} :
intval;
};
class Date {
public:
Date(const Month& m, const Day& d, const Year& y);
...
};
Date d(30, 3, 1995); // error! wrong types
Date d(Day(30), Month(3), Year(1995)); //error! wrong types
Date d(Month(3), Day(30), Year(1995)); //okay, types are correct
class Month {
public:
static Month Jan() { return Month(1); } // 函式而非物件,返回有效月份
static Month Feb() { return Month(2); }
...
static Month Dec() { return Month(12); }
... // 其它成員函式
private:
explicit Month(int m); // 阻止生成新的月份,這是月份專屬資料
...
};
Date d(Month::Mar(), Day(30), Year(1995));
防止可能的客戶錯誤的另一個方法是限制型別內能夠做的事情,常見的限制是加上const。實際上,除非你有很棒的理由,否則就讓你的型別行為與內建型別保持一致。客戶已經知道像 int 這樣的型別如何表現,所以你應該努力使你的型別在合理的前提下有同樣的表現。例如,如果 a 和 b 是 int,給 a*b 賦值是非法的。
避免無端和內建型別不相容的真正原因是為了提供行為一致的介面。很少有特性比一致性更易於引出易於使用的介面,也很少有特性比不一致性更易於加劇介面的惡化。STL容器的介面在很大程度上(雖然並不完美)是一致的,而這使得它們相當易於使用。例如,每一種 STL 容器都有一個名為size的成員函式可以知道容器中有多少物件。與此對比的是 Java,在那裡你對陣列使用length屬性,對String使用length方法,而對List卻要使用size方法,在 .NET 中,Array有一個名為Length的屬性,而ArrayList卻有一個名為Count的屬性。一些開發人員認為整合開發環境(IDEs)能補償這些瑣細的矛盾,但他們錯了。矛盾在開發者工作中強加的精神折磨是任何IDE都無法完全消除的。
任何一個要求客戶記住某些事情的介面都是有錯誤使用傾向的,因為客戶可能忘記做那些事情。例如,條款13介紹的factory函式,它返回一個指向動態分配的 Investment 繼承體系中的物件的指標。
Investment* createInvestment();
為了避免資源洩漏,createInvestment返回的指標最後必須被刪除,但這就為至少兩種型別錯誤創造了機會:刪除指標失敗,或刪除同一個指標一次以上。
你可以將createInvestment的返回值存入一個類似auto_ptr 或tr1::shared_ptr 智慧指標,從而將使用delete的職責交給智慧指標,但仍忘記使用智慧指標,不如讓factory函式在第一現場即返回一個智慧指標:
std::tr1::shared_ptr<Investment>createInvestment();
這就從根本上強制客戶將返回值存入一個tr1::shared_ptr,幾乎完全消除了當底層的 Investment 物件不再使用的時候忘記刪除的可能性。假設從 createInvestment得到一個Investment*指標的客戶期望將這個指標傳給一個名為getRidOfInvestment的函式,而不是對它使用delete。tr1::shared_ptr 提供了一個需要兩個引數(被管理的指標、當引用計數變為零時要呼叫的deleter)的建構函式。這啟發我們建立一個以getRidOfInvestment 為deleter的null tr1::shared_ptr的方法:
std::tr1::shared_ptr<Investment> pInv(0,getRidOfInvestment);
這不會通過編譯。tr1::shared_ptr的建構函式堅決要求它的第一個引數應該是一個指標,而0不是一個指標,它是一個int。當然,它能轉型為一個指標,但那在當前情況下並不夠好,tr1::shared_ptr堅決要求一個真正的指標。用強制轉型解決這個問題,因此createInvestment的實現程式碼看起來是這樣:
std::tr1::shared_ptr<Investment>createInvestment()
{
std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0),
getRidOfInvestment);
retVal =... ; // 令retVal指向正確物件
returnretVal;
}
tr1::shared_ptr的一個特別好的特性是它自動逐指標地使用deleter以消除另一種潛在的客戶錯誤——“cross-DLL問題。”這個問題發生在:一個物件在一個動態連結庫(dynamicallylinked library (DLL))中通過 new 被建立,在另一個不同的 DLL中被刪除。在許多平臺上,這樣的cross-DLL new/delete 對會引起執行時錯誤。tr1::shared_ptr 可以避免這個問題,因為它預設的deleter只將
delete用於這個tr1::shared_ptr被建立的 DLL 中。這就意味著,例如,如果 Stock 是一個繼承自 Investment 的類,而且 createInvestment 被實現如下,std::tr1::shared_ptr<Investment>createInvestment()
{return std::tr1::shared_ptr<Investment>(new Stock);}
返回的tr1::shared_ptr能在DLL之間進行傳遞,而不必關心cross-DLL問題。指向這個 Stock 的 tr1::shared_ptr 將保持對“當這個 Stock 的引用計數變為零的時候,哪一個 DLL 的delete應該被使用”的跟蹤。
tr1::shared_ptr是一個消除某些客戶錯誤的簡單方法,值得我們核計其使用成本。最通用的 tr1::shared_ptr 實現來自於 Boost,其shared_ptr的大小是原始指標的兩倍,以動態分配記憶體用於簿記用途和deleter專屬資料,當呼叫它的deleter時使用一個virtual函式來呼叫,並在多執行緒程式修改引用次數時蒙受執行緒同步化的額外開銷(你可以通過定義一個預處理符號來使多執行緒支援失效。)。在缺點方面,它比一個原始指標大且慢,而且要使用輔助動態記憶體。在許多應用程式中,這些附加的執行時開銷並不顯著,而對客戶錯誤的減少卻是每一個人都看得見的。
相關文章
- 條款04: 確定物件被使用前已被初始化物件
- 被誤刪的檔案正確處理方法,快速找回誤刪的檔案
- Oracle中最容易被忽略的那些實用特性Oracle
- 正確處理被病毒侵入電腦的方法
- keycloak~正確讓api介面支援跨域API跨域
- 如何才能讓傳送簡訊更容易被客戶理解?
- 【DBA】log file switch (checkpoint incomplete) - 容易被誤診的event
- 【04】確定物件被使用之前已先被初始化物件
- 最近被蘋果拒絕的條款和解決方法蘋果
- Vue中那些容易被忽略的~Vue
- 最實用也最容易被遺忘的 Linux 命令列使用技巧Linux命令列
- QT TS檔案翻譯,部分不能正確被翻譯QT
- Stream -- Node.js中最好的卻最容易被誤解的部分Node.js
- Web開發和設計上容易被忽視的8個錯誤Web
- 被誤解的 MVC 和被神化的 MVVMMVCMVVM
- 被誤解的MVC和被神化的MVVMMVCMVVM
- 記Promise一個容易被忽略的特性Promise
- Windows10應用商店被誤刪了怎麼辦 win10應用商店被誤刪如何找回WindowsWin10
- CDN應用進階 | 正確使用CDN 讓你更好規避安全風險
- 如果精確判斷一個IP是否被佔用
- 後臺設計中容易被忽略的坑
- 捋一捋容易被忽略的API用法API
- Java異常處理:如何寫出“正確”但被編譯器認為有語法錯誤的程式Java編譯
- 前端常見bug系列1:容易被誤解的:last-child和:first-child前端AST
- 移動應用推廣中容易被忽略的九大使用者獲取策略
- 使用graphicsmagick時gm命令被佔用
- 商品詳情API介面怎麼被程式猿使用API
- 記介面當中使用 session 是如何被排斥的Session
- 那些容易被忽視的 JavaScript 細節總結JavaScript
- 專案開發中容易被忽視的部分
- Python中容易被忽略的內建型別Python型別
- 正確使用資料架構的五條規則 - infoworld架構
- [譯] 如何讓你的設計系統被廣泛採用
- 埠被佔用
- 糾正對“用正確的工具幹活兒”這句話的誤解
- 被誤讀的設計模式設計模式
- 真正優秀的使用者介面會被無視!
- 正確理解 PHP 錯誤資訊(轉)PHP