C++ 核心指南(C++ Core Guidelines)是由 Bjarne Stroustrup、Herb Sutter 等頂尖 C+ 專家創建的一份 C++ 指南、規則及最佳實踐,旨在幫助大家正確、高效地使用“現代 C++”,
這份指南側重于介面、資源管理、記憶體管理、并發等 High-level 主題,遵循這些規則可以最大程度地保證靜態型別安全,避免資源泄露及常見的錯誤,使得程式運行得更快、更好,
R.smart:智能指標
- R.20:使用 unique_ptr 或 shared_ptr 來表示所有權
- R.21:除非需要共享所有權,否則優先使用 unique_ptr 而不是 shared_ptr
- R.22:使用 make_shared() 創建 shared_ptr
- R.23:使用 make_unique() 創建 unique_ptr
- R.24:使用 std::weak_ptr 打破 shared_ptr 的回圈參考
- R.30: 僅在需要明確表示生命周期語意時才將智能指標作為引數傳遞
- R.31: 如果你使用的是非 std 智能指標,遵循 std 的基本模式
- R.32: 將形參宣告為
unique_ptr<widget>來表明函式對 widget 的所有權 - R.33: 將形參宣告為
unique_ptr<widget>&來表明函式對 widget 的重新賦值語意 - R.34: 將形參宣告為
shared_ptr<widget>來明確表明函式共享所有權 - R.35: 將形參宣告為
shared_ptr<widget>&來表明函式可能重新賦值共享指標 - R.36: 將形參宣告為
const shared_ptr<widget>&來表明它可能增加物件的參考計數 - R.37: 不要傳遞從“智能指標別名”取得的指標或參考
R.20:使用 unique_ptr 或 shared_ptr 來表示所有權
可以避免資源泄露
void f()
{
// bad, p1 可能泄露
X* p1 { new X };
// good, 獨占所有權
auto p2 = make_unique<X>();
// good, 共享所有權
auto p3 = make_shared<X>();
}
代碼檢查
- 如果 new 的結果賦給了裸指標,給出警告
- 如果函式回傳的“擁有指標”賦給了裸指標,給出警告
R.21:除非需要共享所有權,否則優先使用 unique_ptr 而不是 shared_ptr
unique_ptr 在概念上更簡單、可預測(知道何時發生析構),而且更快(無需隱式維護使用計數),
反面例子
增加和維護了一個不必要的參考計數
void f()
{
shared_ptr<Base> base = make_shared<Derived>();
// 在本地使用 base,不需要拷貝,參考計數永遠不會超過 1
} // 銷毀 base
例子
void f()
{
unique_ptr<Base> base = make_unique<Derived>();
// 在本地使用 base
} // 銷毀 base
代碼檢查
如果一個函式使用了一個在函式內部分配的 shared_ptr,但從不回傳該 shared_ptr 或將其傳遞給形參為 shared_ptr& 的函式,則給出警告,建議使用 unique_ptr 替代,
R.22:使用 make_shared() 創建 shared_ptr
make_shared 提供了更簡潔的構造陳述句,而且 make_shared 有機會將參考計數存盤在其關聯物件的相鄰位置,
示例
shared_ptr<X> p1 { new X{2} }; // BAD
auto p = make_shared<X>(2); // GOOD
使用 make_shared() 版本只提到了 X 一次,所以它通常比使用顯式 new 的版本更短,同時也更快,
代碼檢查
如果一個 shared_ptr 是由 new 的結果構造而不是 make_shared,則給出警告,
R.23:使用 make_unique() 創建 unique_ptr
make_unique 提供了更簡潔的構造陳述句,它還確保在復雜運算式中的例外安全,
make_unique 是 C++14 引入的,而 make_shared 在 C++11 就已經有了
示例
// 可行,但重復出現 Foo
unique_ptr<Foo> p {new Foo{7}};
// 更好的寫法:避免重復的 Foo
auto q = make_unique<Foo>(7);
代碼檢查
如果一個 unique_ptr 是由 new 的結果構造而不是 make_unique,則給出警告,
R.24:使用 std::weak_ptr 打破 shared_ptr 的回圈參考
shared_ptr 依賴于參考計數,而回圈結構的參考計數永遠不為 0,因此我們需要一種機制來打破回圈結構,
例子
#include <memory>
class bar;
class foo {
public:
explicit foo(const std::shared_ptr<bar>& forward_reference)
: forward_reference_(forward_reference)
{ }
private:
std::shared_ptr<bar> forward_reference_;
};
class bar {
public:
explicit bar(const std::weak_ptr<foo>& back_reference)
: back_reference_(back_reference)
{ }
void do_something()
{
if (auto shared_back_reference = back_reference_.lock()) {
// 使用*shared_back_reference
}
}
private:
std::weak_ptr<foo> back_reference_;
}
Herb Sutter:有很多人說“打破回圈參考”,但我認為“臨時共享所有權”更準確,
Bjarne Stroustrup:“打破回圈”是必須要做的,“臨時共享所有權”是你如何“打破回圈”,你可以通過使用另一個 shared_ptr 來“臨時共享所有權”,(這里不太好翻譯,貼出原文:breaking cycles is what you must do; temporarily sharing ownership is how you do it. You could “temporarily share ownership” simply by using another
shared_ptr)
代碼檢查
如果可以靜態地檢測到回圈(可能無法實作),就不需要 weak_ptr,
R.30: 僅在需要明確表示生命周期語意時才將智能指標作為引數傳遞
參見 F.7: 對于一般用途,使用 T* 或 T& 引數而不是智能指標
R.31: 如果你使用的是非 std 智能指標,遵循 std 的基本模式
任何多載了一元 * 和 -> 運算子的型別(包括模板及特化模板)都被認為是智能指標:
- 如果它是可復制的,則應被視為 shared_ptr
- 如果它不可復制,則應被視為 unique_ptr
反面例子
// Boost 的 intrusive_ptr
#include <boost/intrusive_ptr.hpp>
void f(boost::intrusive_ptr<widget> p)
{
p->foo();
}
// Microsoft 的 CComPtr
#include <atlbase.h>
void f(CComPtr<widget> p)
{
p->foo();
}
p 是一個共享指標,但在這里沒有用到它的共享性,并且通過值傳遞會導致性能下降;函式只有在需要參與 widget 的生命周期管理的時候使用智能指標,否則,應該使用 widget& 或者 widget*(如果實參可能是 nullptr)作為形參,
這些第三方/自定義的智能指標與 std::shared_ptr 概念一致,因此接下來的規則也適用于其他型別的第三方和自定義智能指標,這對于排查常見的智能指標錯誤、性能問題時非常有用,
R.32: 將形參宣告為 unique_ptr<widget> 來表明函式對 widget 的所有權
R.33: 將形參宣告為 unique_ptr<widget>& 來表明函式對 widget 的重新賦值語意
以這種方式使用 unique_ptr 既可以起到 self-documented 的作用,又可以強制執行函式呼叫的所有權轉移或重新賦值(reseat)語意,
注意:“重新賦值”(reseat)的意思是:使指標或智能指標指向不同的物件,
例子
// 接受 widget 的所有權
void sink(unique_ptr<widget>);
// 僅使用 widget
void uses(widget*);
// 可能重新賦值指標
void reseat(unique_ptr<widget>&);
反面例子
// 通常不是你想要的
void thinko(const unique_ptr<widget>&);
代碼檢查
- 如果一個函式通過左值參考接受
unique_ptr<T>引數,并且在某條代碼路徑上沒有對其進行賦值或呼叫 reset(),則給出警告,建議改為接受T*或T&, - 如果一個函式通過 const 參考接受
unique_ptr<T>引數,則給出警告,建議改為接受 const T* 或 const T&,
R.34: 將形參宣告為 shared_ptr<widget> 來明確表明函式共享所有權
R.35: 將形參宣告為 shared_ptr<widget>& 來表明函式可能重新賦值共享指標
R.36: 將形參宣告為 const shared_ptr<widget>& 來表明它可能增加物件的參考計數
注:“重新賦值”(reseat)的意思是:使參考或智能指標指向不同的物件,
例子
class WidgetUser
{
public:
// WidgetUser 將共享 widget 的所有權
explicit WidgetUser(std::shared_ptr<widget> w) noexcept:
m_widget{std::move(w)} {}
// ...
private:
std::shared_ptr<widget> m_widget;
};
void ChangeWidget(std::shared_ptr<widget>& w)
{
// 改變呼叫者的 widget
w = std::make_shared<widget>(widget{});
}
// 共享所有權,增加參考計數
void share(shared_ptr<widget>);
// 可能重新賦值指標
void reseat(shared_ptr<widget>&);
// 可能增加參考計數
void may_share(const shared_ptr<widget>&);
代碼檢查
- 如果函式通過左值參考接受
shared_ptr<T>引數,并且在某條代碼路徑上沒有對其進行賦值或呼叫 reset(),則給出警告,建議改為接受 T* 或 T&, - 如果函式通過值傳遞或 const 參考接受
shared_ptr<T>引數,并且在某條代碼路徑上沒有將其復制或移動到另一個 shared_ptr,則給出警告,建議改為接受 T* 或 T&, - 如果函式通過右值參考接受
shared_ptr<T>引數,建議改為值傳遞,
R.37: 不要傳遞從“智能指標別名”取得的指標或參考
違反本規則是導致丟失參考計數、產生懸空指標的首要原因,函式應優先考慮傳遞裸指標或參考到呼叫鏈的下游,在呼叫樹的頂部,從智能指標取得裸指標或參考時,需要確保智能指標在呼叫樹內部不會無意中被重置或重新賦值,
注:有時需要對智能指標進行本地拷貝,以保證在函式呼叫樹的整個期間保持物件不被釋放,
例子
// 全域(靜態或堆),或者本地智能指標的別名
shared_ptr<widget> g_p = ...;
void f(widget& w)
{
g();
use(w); // A
}
void g()
{
// 如果這是最后一個指向 widget 的 shared_ptr,銷毀 widget
g_p = ...;
}
下面的代碼無法通過 code review
void my_code()
{
// BAD: 傳遞一個“從非本地智能指標獲得的指標或參考”,可能在 f(或 f 呼叫的函式)中不小心被重置
f(*g_p);
// BAD: 原因同上,只是作為 "this" 指標傳遞
g_p->func();
}
解決辦法:拷貝一份副本,確保函式呼叫樹整個期間參考計數不為 0
void my_code()
{
// 參考計數增加了 1,可以覆寫整個函式執行及呼叫樹
auto pin = g_p;
// GOOD: 傳遞一個從“本地非別名的指標指標”獲得的指標或參考
f(*pin);
// GOOD: 同上
pin->func();
}
代碼檢查
- 如果在函式呼叫程序中使用的指標或參考是從一個非本地的 shared_ptr 或 unique_ptr 取得,或者從一個本地、但可能是別名的智能指標取得,給出警告,
- 如果是 shared_ptr,建議保存一個本地副本,然后從該副本獲取指標或參考,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/556465.html
標籤:其他
上一篇:2023年6月隨筆暨半年總結
下一篇:返回列表
