條款18:讓接口更容易被正確使用,不易被誤用
? ? ? ? ? 設計接口的原則:正確性、高效性、封裝性、維護性、延展性以及協議的一致性;
? ? ? ? ? 設計原則:1)導入新類型來預防很多客戶端的錯誤,多使用系統類型(type system(劃分為多個explicit函數),如果某個變量的可選情況有限,可以通過static函數來替代對象,表示某個特定的對象,并將對象的構造函數定義成private來防止新的對象生成,具體代碼如下:
class? month{
public:
? ? ? ? ? ?static month Jan( ) {return month(1);}//以函數替代對象,來代替某個特定的月份
? ? ? ? ? ...
private:
? ? ? ? ?explicit month(int m);
};
?date(month::Jan( ));//調用
? ? ? ? ? ? 2)限制類型內,什么事情可以做,什么事情不可以做,通常加上限制const;
? ? ? ? ? ? 3)讓你的types的行為和內置types行為一致,提供行為一致的接口;
? ? ? ? ? ? 4)使用智能指針來管理系統資源,可以避免內存泄露(注意智能指針的管理方式和管理條件(單個對象),并用自定義的deleter(資源釋放函數)綁定在智能指針上,deteler(可缺省)),代碼如下:
?std::tr1::shared_ptr<investment>pinv(static_cast<investment*>(0),getridofinvestment);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //shared_ptr第一個參數必須要求是一個指針,一個空指針shared_ptr
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //getridofinvestment為刪除器
這樣的初始化不是最優的:先把pinv初始化為nullptr后對其進行賦值操作(相當于初始化過程是多余的,如果知道初始指針的話,直接用初始指針來初始化pinv才是最優的);
? ? ? ?使用智能指針的另外一個好的特性,它會自動使用它的各一個指針專屬的刪除器,因而消除一個潛在的客戶錯誤(cross-DLL problem),即;對象在動態連接程序庫(DLL)中被new創建,卻在另外一個DLL內被delete,這一類稱為“跨DLL之new/delete成對運用”會導致運行期錯誤,但是智能指針沒有這個問題,它缺省的刪除器來自于智能指針誕生的那個delete;
? ? ? Boost的shared_ptr指針是原始指針(raw pointer)的兩倍大,以動態分配內存作為簿記用途和“刪除器之專屬數據”,以vrtual形式調用刪除器,并以多線程程序修改引用次數時承受線程同步化的額外開銷,但這些額外的執行成本并不顯著,但是在降低客戶錯誤的方面卻起到了很好的效果;
條款19:設計class猶如設計type
? ? ? ? ? ? 在設計一個新的class的時候,就像設計一個新的type,在設計一個高效的class的時候,應該考慮的問題有:
? ? ? ? ? ? ? 1)對象如何被創建和銷毀;
? ? ? ? ? ? ? 2)對象初始化和賦值之間有什么差別;
? ? ? ? ? ? ? 3)新對象在傳值和傳引用方面的考慮;
? ? ? ? ? ? ? 4)對于成員變量引申的成員函數需要進行的錯誤檢查工作及函數拋出異常及異常處理;
? ? ? ? ? ? ? 5)考慮類型的繼承和被繼承情況;
? ? ? ? ? ? ? 6)類型需要做什么樣的轉換,編寫對應的類型轉換函數;
? ? ? ? ? ? ?7)什么函數應該是成員函數,什么不是;
? ? ? ? ? ? ? 8)什么函數應該定義為private;9)成員變量的屬性;
? ? ? ? ? ? ? 9)保證效率、異常安全性以及資源利用(多任務鎖定和動態內存);
? ? ? ? ? ? ?10)一般化你的類型,考慮定義一個模板類;
? ? ? ? ? ? ?11)是否需要定義類,可以在別人類的基礎上添加一個或者多個non-member函數或者template;
條款20:寧以pass-by-reference-to-const替換pass-by-value
? ? ? ? ?缺省情況下C++以by-value方式傳遞對象到函數,函數參數都是以實際實參的副本為初值,而調用端所獲得的也是函數返回值的一個附件,這些副本由對象的copy構造函數產生,使得pass-by-value稱為一個函數操作,如果有很多成員變量的時候;
? ? ? ? ?如果采用傳遞引用的方式,沒有任何的構造函數或者析構函數調用,因為沒有任何的對象被創建;除此之外,以傳引用的方式傳遞參數可以避免slicing(對象切割)的問題,即當一個對derived class對象以by value的方式傳遞并視為一個base class對象,base class的拷貝構造函數被調用,但是derived class區別于base class的部分會被切割掉,而只留下一個base class;
? ? ? ? 避免是slicing(切割)問題的方法,就是采用by-reference-to-const的方式傳遞,原因是因為reference往往是以指針的形式實現,pass by reference通常意味著傳遞的是指針;
? ? ? ?一般而言,對于內置類型、STL的迭代器和函數對象,采用傳值比較方便,其他最好使用傳引用;
條款21:必須返回對象時,別妄想返回其reference
? ? ? ? ? 對于reference只是一個名稱,代表某個既有對象。任何時候看到一個reference聲明式,應該去了解它的另外一個名稱是什么?
? ? ? ? 分析代碼如下:
//返回引用指向棧上的變量
錯誤方法1.const rational& operator*(const rational&lhs,const rational &rhs){
? ? ? ? ?rational result(lhs.n*rhs.n,rhs.d*rhs.d);//試圖返回一個棧空間變量為引用
? ? ? ? ? return result;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//對象在函數調用結束后被銷毀,引用指向一個錯誤的對象;
}
//返回引用指向堆上的變量
錯誤方法2.const rational& operator*(const rational&lhs,const rational &rhs){
? ? ? ? ?rational *result=new rational(lhs.n*rhs.n,rhs.d*rhs.d);//如何對這個new內存進行管理
? ? ? ? ? return *result;? ? ? ? ? ? ?//沒辦法取得reference背后的指針,如何a*b*c時更容易出錯;
}
//返回引用指向一個函數內部的static rational對象
錯誤方法3.const rational& operator*(const rational&lhs,const rational &rhs){
? ? ? ? ?static rational result;? ? //staitc對象會導致多線程安全性
? ? ? ? ?result=rational temp(lhs.n*rhs.n,rhs.d*rhs.d);//static公用問題會帶來錯誤的影響;
? ? ? ? ? return result;? ? ? ? ? ??
}
rational a,b,c,d; if(a*b==c*d)//if里面的數據一直為真,不管其他的怎么取值,因為static原因和返回是引用的原因
正確代碼:inline const rational operator*(const rational&lhs,const rational &rhs){
? ? ? ? ? ??rational result(lhs.n*rhs.n,rhs.d*rhs.d);//雖然會有拷貝構造和析構的成本,但是對于安全性考慮卻是一個很好的做法;
}
結論:不要返回pointer或者reference指向一個local stack對象(見錯誤方法1),或返回一個reference指向一個heap-allocated對象9見錯誤方法2),或返回pointer或reference指向一個local static對象而有可能同時需要多個這樣的對象(見錯誤方法3);
條款22:將成員變量聲明為private
? ? ? ? ? 如果成員變量不是public,客戶能夠訪問對象的方式就是通過成員變量函數,如果public接口內的每樣東西都是函數,那么可以避免客戶在調用class成員時考慮是否需要使用小括號;如果你通過函數訪問成員變量,那么你以后你改變成員變量時,class客戶卻完全不知道class 內部發生了變化;
? ? ? ? 將成員變量隱藏在函數接口的背后沒可以為“所有可能的實現”提供彈性;你對客戶隱藏成員變量(封裝性),你可以確定class的約束條件得到維護,因為只有成員函數可以影響它們;
? ? ? ? 某個東西的封裝性與“當其內容改變時可能造成的代碼破壞量”成反比,改變就是從class中移除它;
? ? ? ?一旦你聲明成員變量為public或者protected時,就很難改變這個成員變量涉及的一切,有太多的代碼需要重寫、重新測試、重新編寫、重新編譯;
? ? ? ?結論:將成員變量聲明為private,這可以賦予客戶訪問數據的一致性、可細微劃分訪問控制、允諾約束條件獲得保證,并為class提供充分的實現彈性;
? ? ? ? ?? ? ? ? ? ?