1. 程式人生 > >std::make_shared有啥用

std::make_shared有啥用

http://bitdewy.github.io/blog/2014/01/12/why-make-shared/

C++11 中引入了智慧指標, 同時還有一個模板函式 std::make_shared 可以返回一個指定型別的 std::shared_ptr, 那與std::shared_ptr 的建構函式相比它能給我們帶來什麼好處呢 ?

優點

效率更高

shared_ptr 需要維護引用計數的資訊,

  • 強引用, 用來記錄當前有多少個存活的 shared_ptrs 正持有該物件. 共享的物件會在最後一個強引用離開的時候銷燬( 也可能釋放).
  • 弱引用, 用來記錄當前有多少個正在觀察該物件的 weak_ptrs. 當最後一個弱引用離開的時候, 共享的內部資訊控制塊會被銷燬和釋放 (共享的物件也會被釋放, 如果還沒有釋放的話).

如果你通過使用原始的 new 表示式分配物件, 然後傳遞給 shared_ptr (也就是使用 shared_ptr 的建構函式) 的話, shared_ptr 的實現沒有辦法選擇, 而只能單獨的分配控制塊:

1
2
auto p = new widget();
shared_ptr sp1{ p }, sp2{ sp1 };

如果選擇使用 make_shared 的話, 情況就會變成下面這樣:

1
auto sp1 = make_shared(), sp2{ sp1 };

記憶體分配的動作, 可以一次性完成. 這減少了記憶體分配的次數, 而記憶體分配是代價很高的操作.

異常安全

看看下面的程式碼:

1
2
3
4
void F(const std::shared_ptr<Lhs>& lhs, const std::shared_ptr<Rhs>& rhs) { /* ... */ }
F(std::shared_ptr<Lhs>(new Lhs("foo")),
  std::shared_ptr<Rhs>(new Rhs("bar")));

C++ 是不保證引數求值順序, 以及內部表示式的求值順序的, 所以可能的執行順序如下:

  1. new Lhs(“foo”))
  2. new Rhs(“bar”))
  3. std::shared_ptr
  4. std::shared_ptr

好了, 現在我們假設在第 2 步的時候, 丟擲了一個異常 (比如 out of memory, 總之, Rhs 的建構函式異常了), 那麼第一步申請的 Lhs 物件記憶體洩露了. 這個問題的核心在於, shared_ptr 沒有立即獲得裸指標.

我們可以用如下方式來修復這個問題.

1
2
3
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);

當然, 推薦的做法是使用 std::make_shared 來代替:

1
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));

缺點

建構函式是保護或私有時,無法使用 make_shared

物件的記憶體可能無法及時回收

make_shared 只分配一次記憶體, 這看起來很好. 減少了記憶體分配的開銷. 問題來了, weak_ptr 會保持控制塊(強引用, 以及弱引用的資訊)的生命週期, 而因此連帶著保持了物件分配的記憶體, 只有最後一個 weak_ptr 離開作用域時, 記憶體才會被釋放. 原本強引用減為 0 時就可以釋放的記憶體, 現在變為了強引用, 若引用都減為 0 時才能釋放, 意外的延遲了記憶體釋放的時間. 這對於記憶體要求高的場景來說, 是一個需要注意的問題. 關於這個問題可以看這裡 make_shared, almost a silver bullet

參考