目錄
基本用法
2.1 解決傳統構造方式的問題
2.2 標準委員會的動機
3.1 性能優勢(最重要優點)
內存分配優化:
性能提升表現:
3.2 異常安全(關鍵優勢)
3.3 代碼簡潔性(顯著優勢)
配合 auto 使用更清晰:
4.1 典型實現偽代碼
1.?make_shared 的基本概念
make_shared
?是 C++11 引入的模板函數,用于創建并返回一個指向動態分配對象的?shared_ptr
。它是創建共享所有權對象的標準推薦方式。
基本用法
auto ptr = std::make_shared<MyClass>(arg1, arg2...);
2. 引入 make_shared 的主要原因
2.1 解決傳統構造方式的問題
傳統?shared_ptr
?構造方式:
std::shared_ptr<MyClass> ptr(new MyClass(arg1, arg2...));
這種方式存在三個潛在問題:
- 內存分配分離:需要兩次內存分配(對象+控制塊)
- 異常不安全:可能在構造過程中發生內存泄漏
- 代碼冗余:需要重復類型名稱
異常導致泄漏的場景
當執行?std::shared_ptr<MyClass> ptr(new MyClass(arg1, arg2...))
?時,實際分為兩個關鍵內存操作:
- 執行?
new MyClass(arg1, arg2...)
:分配 “對象本身” 的內存,并調用構造函數初始化?MyClass
?對象。 - 構造?
shared_ptr
:分配 “控制塊” 的內存(控制塊用于記錄引用計數、弱引用計數等元信息),然后讓?shared_ptr
?接管第一步創建的對象。
如果在 ** 步驟 2(分配控制塊)** 時發生異常(比如內存不足,拋出?std::bad_alloc
),問題就會出現:
- 步驟 1 已經成功創建了?
MyClass
?對象(內存已分配,構造函數已執行)。 - 但步驟 2 失敗,
shared_ptr
?沒有被成功構造,也就沒有指針能管理第一步創建的對象。 - 最終,這個?
new
?出來的?MyClass
?對象既沒有被智能指針接管,也沒有被手動?delete
,導致內存泄漏(對象的內存永遠無法被釋放)。
2.2 標準委員會的動機
C++標準委員會引入 make_shared 主要基于:
- 性能優化:減少內存分配次數
- 異常安全:保證原子性操作
- 代碼簡潔:改善編碼體驗
3. make_shared 的核心優勢
3. make_shared 的核心優勢
3.1 性能優勢(最重要優點)
內存分配優化:
典型實現的內存布局:
make_shared 分配的內存塊:
+-------------------+-------------------+
| 控制塊 (refcount) | 對象數據 |
+-------------------+-------------------+
性能提升表現:
- 緩存友好:控制塊和對象在同一緩存行
- 減少碎片:單次分配減少內存碎片
- 分配更快:內存分配是昂貴操作
基準測試通常顯示?make_shared
?比傳統方式快 10-30%
3.2 異常安全(關鍵優勢)
考慮以下可能拋出異常的代碼:
void process(std::shared_ptr<X> x, std::shared_ptr<Y> y);process(std::shared_ptr<X>(new X), std::shared_ptr<Y>(new Y));
問題在于:
new X
?成功new Y
?拋出異常- 已分配的 X 內存泄漏
解釋
- 先執行?
new X
,成功分配了?X
?類型的內存,并得到裸指針?X*
; - 接著執行?
new Y
,但此時內存不足(或其他原因),new Y
?拋出異常(比如?std::bad_alloc
)。
異常導致 “裸指針未被智能指針接管”
當?new Y
?拋出異常時,整個?process
?函數的調用會被中斷。此時:
- 第一個參數?
std::shared_ptr<X>(new X)
?還沒完成 “shared_ptr
?封裝裸指針” 的過程(因為參數計算被異常打斷了); - 也就是說,
new X
?分配的內存,只有裸指針,沒有被?shared_ptr
?接管(智能指針的 “自動釋放” 能力沒生效)。
使用?make_shared
?的解決方案:
process(std::make_shared<X>(), std::make_shared<Y>());
這樣要么全部成功,要么全部回滾,不會泄漏資源。
3.3 代碼簡潔性(顯著優勢)
// 傳統方式(重復類型名)
std::shared_ptr<VeryLongTypeName> p(new VeryLongTypeName(args...));// make_shared 方式(簡潔)
auto p = std::make_shared<VeryLongTypeName>(args...);
配合 auto 使用更清晰:
auto widget = std::make_shared<Widget>(color, size);
4. make_shared 的實現原理
4.1 典型實現偽代碼
//make_shared的實現
//偽代碼
template<typename T, typename... Args>
shared_ptr<T> my_make_shared(Args&& ...args)
{//先開辟控制塊和對象大小的空間void* p = ::operator new(sizeof(ContorlBlock) + sizeof(T));//構造控制塊ContorlBlock* cb = new(p) ControlBlock();//獲取到對象的地址T* obj = reinterpret_cast<T*>(static_cast<char*>(p) + sizeof(ContorlBlock));//給對象進行構造 使用定位new,使用完美轉發保持右值屬性new(obj) T(std::forward(Args)(args)...);return shared_ptr<T>(cb, obj);
}