C++ 核心指南(C++ Core Guidelines)是由 Bjarne Stroustrup、Herb Sutter 等頂尖 C++ 專家創建的一份 C++ 指南、規則及最佳實踐,旨在幫助大家正確、高效地使用“現代 C++”,
這份指南側重于介面、資源管理、記憶體管理、并發等 High-level 主題,遵循這些規則可以最大程度地保證靜態型別安全,避免資源泄露及常見的錯誤,使得程式運行得更快、更好,
R.alloc: 分配和釋放
- R.10: 避免使用
malloc()/free() - R.11: 避免顯式呼叫
new/delete - R.12: 顯式資源分配的結果應立即給到資源管理物件
- R.13: 在一條陳述句中,最多只能有一個顯式資源分配
- R.14: 避免使用
[]引數,用span替代 - R.15: 分配/釋放操作要成對多載
R.10: 避免使用 malloc() / free()
malloc() / free() 不支持構造、析構,不要和 new / delete 混用,
例子
class Record {
int id;
string name;
};
void use()
{
// p1 可能是 nullptr;*p1 未初始化,尤其是其中的 name 不是一個合法的 string 物件
Record* p1 = static_cast<Record*>(malloc(sizeof(Record)));
// 除非拋例外,*p2 默認初始化
auto p2 = new Record;
// p3 可能是 nullptr;如果不為空,*p3 默認初始化
auto p3 = new(nothrow) Record;
delete p1; // error: 不能 delete 由 malloc() 回傳的指標
free(p2); // error: 不能 free() new 出來的物件
}
最后的 delete、free 在有的實作中可能正常作業,有的會導致運行時錯誤,
例外
有的應用中禁止例外,如 life-critical 和硬實時系統,但是很多針對例外的禁用只是迷信,或是擔心導致舊代碼資源管理上的混亂,如果是這種情況,可以考慮 nothrow 版本的 new
代碼檢查建議
標記顯式的 malloc/free 呼叫
R.11: 避免顯式呼叫 new / delete
new 回傳的指標應該屬于資源句柄(在資源句柄的析構中自動呼叫 delete),如果 new 回傳值賦給了裸指標,可能導致資源泄露,
注
在大型專案中,如果在應用代碼中(而不是在專門資源管理類中)出現 delete,那多半會有 bug:如果代碼里有幾處 delete 呼叫,你怎么保證沒有多呼叫或者少呼叫?這類 bug 不一定能立即發現,可能在潛伏一段時間后,在某次代碼維護/重構時暴露,
代碼檢查建議
針對顯式的 new / delete 給出警告,建議使用 make_unique 替代
R.12: 顯式資源分配的結果應立即給到資源管理物件
否則,一旦拋例外或回傳將導致資源泄露
反面例子
void func(const string& name)
{
// 打開檔案
FILE* f = fopen(name, "r");
vector<char> buf(1024);
// 關閉檔案
auto _ = finally([f] { fclose(f); });
// ...
}
buf 分配空間可能失敗拋例外,導致 f 檔案句柄泄露
正面例子
void func(const string& name)
{
ifstream f{name};
vector<char> buf(1024);
// ...
}
檔案句柄在 ifstream 內部,ifstream 銷毀時自動 fclose 檔案句柄,簡單、安全、高效,
代碼檢查建議
標記那些用來初始化指標的顯式資源分配
R.13: 在一條陳述句中,最多只能有一個顯式資源分配
如果在一條陳述句中執行兩個顯式資源分配,可能導致資源泄露,因為很多子運算式的求值順序(包括函式引數)是未定義的,
例子
void fun(shared_ptr<Widget> sp1, shared_ptr<Widget> sp2);
如果像下面這樣呼叫 fun():
// BAD: 可能泄露
fun(shared_ptr<Widget>(new Widget(a, b)), shared_ptr<Widget>(new Widget(c, d)));
上述呼叫是“例外不安全”(exception-unsafe)的,因為編譯器可能會對創建兩個引數的運算式重新排序,特別是編譯器可能交叉執行兩個子運算式:先給 sp1、sp2 分配記憶體空間、然后呼叫 Widget 的構造,如果此時在構造某一個引數的時候拋出例外,則另一個物件的記憶體不會被釋放!
解決這個問題也很簡答,不在一條陳述句里出現多個顯式資源分配即可,例如;
// 稍好,但有點亂
shared_ptr<Widget> sp1(new Widget(a, b));
fun(sp1, new Widget(c, d));
最好的辦法是完全避免顯式資源分配,而是通過工廠函式回傳擁有的物件:
// 最佳實踐
fun(make_shared<Widget>(a, b), make_shared<Widget>(c, d));
如果沒有像 make_shared、make_unique 這樣的工廠函式,自己封裝一個,
代碼檢查建議
如果一條陳述句內有多個顯式資源分配,標記該陳述句
R.14: 避免使用 [] 引數,用 span 替代
陣列形參退化為指標,丟失陣列大小資訊,容易導致邊界錯誤,用 span 可以保留陣列大小資訊,
例子
// 不推薦
void f(int[]);
// 不推薦指標指向多個物件
// 指標應該指向單個物件(見 R.2)
void f(int*);
// 推薦
void f(gsl::span<int>);
R.15: 分配/釋放操作要成對多載
否則將導致混亂
例子
class X {
void* operator new(size_t s);
void operator delete(void*);
};
注
如果希望記憶體不被釋放,用 =delete 明確禁止釋放操作,
代碼檢查建議
標記不成對的分配/釋放操作
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/555948.html
標籤:其他
下一篇:返回列表
