設計與宣告
條款18:讓介面容易被正確使用,不易被誤用
【Summary】
1. 好的介面很容易被正確使用,不容易被誤用。你應該在你的所有介面中努力達成這些性質。
2. “促進正確使用”的辦法包括介面的一致性,以及與內建型別的行為相容。
3. “阻止誤用”的辦法包括建立新型別、限制型別上的操作,束縛物件值,以及消除客戶的資源管理責任。
4. tr1::shared_ptr支援定製刪除器。這可防範DLL問題,可被用來自動解除互斥量等等。
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 v){ // point1 : 建構函式私有化
var = v;
}
int var;
};
struct Year{
int var;
explicit Year(int v): var(v){} // point2 : explicit 避免隱式轉型
};
struct Day{
int var;
explicit Day(int v): var(v){}
};
class Date{
public:
/* point3 */
explicit Date(const Month& m, const Day& d, const Year& y):
month_(m),day_(d),year_(y){}
private:
Month month_;
Year year_;
Day day_;
};
int main (){
Date d(Month::dec(), Day(12),Year(2016)); // point3, 限制初始化的格式,減少錯誤可能
return 0;
}
class Example{
public:
Example( ){}
~Example(){ printf("destructor.
");}
};
/* bad
Example* getExample(){
return new Example();
}
*/
boost::shared_ptr<Example> getExample(){ // tr1::shared_ptr支援定製刪除器
return boost::shared_ptr<Example>(new Example());
}
條款19:設計class猶如設計type
【設計規範】:
1. 新型別的物件應該如何被建立和銷燬
全域性變數、區域性變數、static變數、動態分配
2. 物件的初始化和物件的賦值該有什麼樣的差別
賦值操作符、複製建構函式等
3. 新型別的物件如果被passed by value(值傳遞),意味著什麼
4. 什麼是新型別的“合法值”
成員變數的取值問題
5. 你的新型別需要配合某個繼承圖系嗎
6. 你的新型別需要什麼樣的型別轉換
7. 什麼樣的操作符和函式對此新型別而言是合理的
操作符過載
8. 什麼樣的標準函式應該駁回
宣告為private的函式
9. 誰該取用新型別的成員
1.成員應該屬於public、private還是protected 2.與friends類的耦合
10. 什麼是新型別的“未宣告介面”
暫略
11. 你的新型別有多少一般化
是否需要應該使用template
12. 你真的需要一個新型別嗎
**
`
條款20: 寧以pass-by-reference-to-const 替換pass-by-value
1. 儘量以pass-by-reference-to-const替代pass-by-value。前者通常比較高效,並可避免切割問題。
2. 以上規則並不使用於內建型別,以及STL的迭代器和函式物件。對它們而言,pass-by-value往往比較適當
條款21: 必須返回物件時,別妄想返回其reference
1. 利用堆不行,利用static也不行
條款22: 將成員變數宣告為private
1. 切記將成員變數宣告為private
2. protected 並不比public更具封裝性
條款23:寧以non-member、non-friend 替換member函式
1. 成員函式不僅可以訪問private成員變數,也可以取用private函式、enum、typedef等等。
而非成員非友元函式能實現更大的
封裝性
,因為它只能訪問public函式。
2. 將所有便利函式(使用non-member non-friend的形式)放在多個標頭檔案內但隸屬同一個名稱空間,
意味客戶可以輕鬆擴充套件這一組便利函式
條款24:若所有引數皆需型別轉換,請為此採用non-member函式
如果你需要為某個函式的所有引數(包括被this指標所指的那個隱喻引數)進行型別轉換,那麼這個函式必須是個non-member。
條款25: 考慮寫出一個不丟擲異常的swap函式
1. 當std::swap對你的型別效率不高時,提供一個swap成員函式,並確定這個函式不丟擲異常
2. 如果你提供一個member swap 【point 1】,也該提供一個non-member swap 【point 2】用來呼叫前者。
3. 對於class(而非templates),也請特化std::swap【point 3】。
4. 呼叫swap時應針對std::swap使用using宣告式,然後呼叫swap並且不帶任何“名稱空間資格修飾”。【point 4】
5. 為“使用者定義型別”進行std templates全特化是好的,但千萬不要嘗試在std內加入某些對std而言全新的東西。【point 5】
namespace mynamespace{
template<typename T>
class Example{
public:
Example(const T& v):var(v){}
void swap(Example& e){ // point 1
using std::swap;
swap(this->var, e.var);
}
const T getVar()const {
return var;
}
private:
T var;
};
template<typename T> // point 2, swap不是過載,也不是特化
void swap(mynamespace::Example<T>& e1, mynamespace::Example<T>& e2){
e1.swap(e2);
}
template<>
void Example<char>::swap(Example<char>& e){
std::cout<<"special for char"<<std::endl;
using std::swap; // point 4
swap(this->var, e.var);
}
}
namespace std{ // point 3 & 5,函式模板std::swap特化,不能對std::swap過載
template<>
void swap<mynamespace::Example<char> >(
mynamespace::Example<char>& e1,
mynamespace::Example<char>& e2)
{
e1.swap(e2);
}
}
#include <iostream>
using namespace mynamespace;
template<typename T>
std::ostream& operator<<(std::ostream& os, Example<T>& e){
os<<e.getVar();
}
int main(){
Example<char> e1(`1`) , e2(`2`);
swap(e1,e2);
std::cout<<e1<<std::endl;
std::cout<<e2<<std::endl;
return 0;
}