Effective C++(18) 讓接口更容易被正確使用,不易被誤用

問題聚焦:
    從這個條款開始,我們把注意力轉移到軟件設計和聲明上來,具體的說就是,C++接口的設計和聲明。
    所謂軟件設計,就是以一般習慣的構想開始,演變成細節的實現,最終開發針對性的特殊接口。
    接口的設計和聲明的一個最基本的準則是:讓接口容易被正確使用,不容易被誤用。
    引入新類型是預防接口被誤用的常見手段之一。


欲開發一個好的接口,首先必須考慮客戶可能做出什麼樣的錯誤。
來看下面這個例子
class Date
{
public:
    Date(int month, int day, int year);
    ... ...
};
上面這個接口有什麼問題呢?(如果你覺得沒問題的話,那麼就該像我一樣老實地看下去。。)
它的客戶很容易犯下至少兩個錯誤:
  1. 他們也許會以錯誤的次序傳遞參數,如:Date d(30, 3, 1995);
  2. 他們可能傳遞一個無效的月份或天數,如:Date d(2, 30, 1995);
許多像這類客戶端錯誤,可以通過引入新類型獲得預防。
struct Day {
    explicit Day(int d) : val(d) { }
    int val;
};
struct Month{
    explicit Month (int d) : val(d) { }
    int val;
};
struct Year{
    explicit Year (int d) : val(d) { }
    int val;
};

class Date {
public:
    Date(const Month& , const Day& d, const Year& y);
    ... ...
};

// 使用
Date d(30, 3, 1995);                     // error,類型錯誤
Date d(Day(30), Month(3), Year(1995));    // error,類型錯誤
Date d(Month(3), Day(30),  Year(1995));
預防客戶錯誤的另一個辦法是,限制類型內什麼事可做,什麼事不能做。
常見的限制是爲函數或者重載操作符的返回值加上const。


讓types容易被正確使用,不容易被誤用。
下面主要通過智能指針std::tr1::shared_ptr的用法來說明這個思想。

任何接口如果要求客戶必須記得做某些事情,就是有着被不正確使用的傾向,因爲客戶可能會忘記做那件事情。
如下這個工廠函數(參數省略)
Inverstment* createInvestment();
錯誤傾向:沒有刪除指針,或刪除同一個指針超過兩次。
方案:在條款13中已經告訴我們如何將createInvestment的返回值存儲與一個智能指針,以防止資源泄漏。
錯誤傾向:客戶忘記使用智能指針。
方案:先發制人——較佳接口的設計原則之一,令工廠函數返回一個智能指針。
std::tr1::shared_ptr<Investment> createInvestment();

回到原始的返回Investment*指針的版本,這時,有的客戶希望通過函數getRidOfIvestment來析構這個資源,而不是直接的delete。
錯誤傾向:企圖使用錯誤的資源析構機制,用delete而不是getRidOfInvestment
方案:使用str1::shared_ptr提供的第二個參數:自定義的刪除器
//創建一個null shared_ptr指針,並自帶一個刪除器
std::tr1::shared_ptr<Investment> pInv(0, getRidOfInvestment);   // error!第一個參數應該是一個指針,而不是int
// 使用轉換cast
std::tr1::shared_ptr<Investment> pInv( static_cast<Investment*>(0), getRidOfInvestment );

// 基於上面的定製的tr1::shared_ptr來解決上面的這個錯誤傾向
std::tr1::shared_ptr<Investment> createInvestment()
{
    std::str1::shared_ptr<Investment> retVal(static_cast<Investment*>(0), getRidOfInvestment);
    retVal = ... ;       // 令retVal指向正確對象
    return retVal;
}
錯誤傾向:cross_DLL problem,即對象在動態連接程序(DLL)中被new創建,卻在另一個DLL內被delete銷燬。
方案:tr1::shared_ptr沒有這個問題,因爲它的刪除器是自帶的(第二個參數默認指定的),所以不會被別的DLL銷燬。

tr1::shared_ptr看起來在“降低客戶錯誤”方面的功能很強大,但是其代價就是該指針比原始指針大且慢,而且使用輔助動態內存。
所以如何使用,視使用場景具體判斷。



小結:
  • 好的接口很容易被正確使用,不容易被誤用。應該在接口設計時努力打到這點。
  • 促進正確使用的辦法包括接口的一致性,以及與內置類型的行爲兼容
  • 阻止誤用的辦法包括建立新類型、限制類型上的操作,束縛對象值,以及消除客戶的資源管理責任。
  • tr1::shared_ptr支持定製刪除器,這可防範DLL問題,可被用來自動解除互斥鎖問題。(Effective C++(14) 在資源管理類中小心copying行爲

參考資料:
《Effective C++ 3rd》
發佈了95 篇原創文章 · 獲贊 45 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章