make_shared和shared_ptr的區別


 

make_shared和shared_ptr的區別

struct A;
std::shared_ptr<A> p1 = std::make_shared<A>();
std::shared_ptr<A> p2(new A);

上面兩者有什么區別呢? 區別是:std::shared_ptr構造函數會執行兩次內存申請,而std::make_shared則執行一次。

std::shared_ptr在實現的時候使用的refcount技術,因此內部會有一個計數器(控制塊,用來管理數據)和一個指針,指向數據。因此在執行std::shared_ptr<A> p2(new A)的時候,首先會申請數據的內存,然后申請內控制塊,因此是兩次內存申請,而std::make_shared<A>()則是只執行一次內存申請,將數據和控制塊的申請放到一起。那這一次和兩次的區別會帶來什么不同的效果呢?

異常安全

考慮下面一段代碼:

void f(std::shared_ptr<Lhs> &lhs, std::shared_ptr<Rhs> &rhs){...}

f(std::shared_ptr<Lhs>(new Lhs()),
  std::shared_ptr<Rhs>(new Rhs())
);

因為C++允許參數在計算的時候打亂順序,因此一個可能的順序如下:

  1. new Lhs()
  2. new Rhs()
  3. std::shared_ptr
  4. std::shared_ptr

此時假設第2步出現異常,則在第一步申請的內存將沒處釋放了,上面產生內存泄露的本質是當申請數據指針后,沒有馬上傳給std::shared_ptr,因此一個可能的解決辦法是:

auto lhs = std::shared_ptr<Lhs>(new Lhs());
auto rhs = std::shared_ptr<Rhs>(new Rhs());
f(lhs, rhs);

而一個比較好的方法是使用std::make_shared

f(std::make_shared<Lhs>(),
  std::make_shared<Rhs>()
);

make_shared的缺點

因為make_shared只申請一次內存,因此控制塊和數據塊在一起,只有當控制塊中不再使用時,內存才會釋放,但是weak_ptr卻使得控制塊一直在使用。

什么是weak_ptr?

weak_ptr是用來指向shared_ptr,用來判斷shared_ptr指向的數據內存是否還存在了(通過方法lock),下面是一段示例代碼:

#include <memory>
#include <iostream>
using namespace std;
struct A{
    int _i;
    A(): _i(int()){}
    A(int i): _i(i){}
};

int main()
{
    shared_ptr<A> sharedPtr(new A(2));
    weak_ptr<A> weakPtr = sharedPtr;
    sharedPtr.reset(new A(3)); // reset,weakPtr指向的失效了。
    cout << weakPtr.use_count() <<endl;
}

通過lock()來判斷是否存在了,lock()相當於

expired()?shared_ptr<element_type>() : shared_ptr<element_type>(*this)

當不存在的時候,會返回一個空的shared_ptr,weak_ptr在指向shared_ptr的時候,並不會增加ref count,因此weak_ptr主要有兩個用途:

  1. 用來記錄對象是否存在了
  2. 用來解決shared_ptr環形依賴問題

weak_ptr解決環形依賴

下面是存在環形依賴的代碼:

include <memory>
include <iostream>

using namespace std;
struct B;
struct A { shared_ptr<B> b;};
struct B { shared_ptr<A> a;};


int main()
{
    shared_ptr<A> x(new A);
    //x->b = new B; // wrong
    //x->b = shared_ptr<B>(new B);
    x->b = make_shared<B>();
    x->b->a = x;
    cout << x.use_count() <<endl;
    cout << x->b.use_count() <<endl;
    // Ref count of 'x' is 2.
    // Ref count of 'x->b' is 1.
    // When 'x' leaves the scope, there will be a memory leak:
    // 2 is decremented to 1, and so both ref counts will be 1.
    // (Memory is deallocated only when ref count drops to 0)
}

下面是解決方案:

shared_ptr<A> x(new A);
//x->b = new B; // wrong
//x->b = shared_ptr<B>(new B);
x->b = make_shared<B>();
x->b->a = x;
cout << x.use_count() <<endl;
cout << x->b.use_count() <<endl;
// Ref count of 'x' is 1.
// Ref count of 'x->b' is 1.
// When 'x' leaves the scope, its ref count will drop to 0.
// While destroying it, ref count of 'x->b' will drop to 0.
// So both A and B will be deallocated.   cout << x->b.use_count() <<endl;

一個自然而然的問題是:weak_ptr是否能夠當編程人員不清楚擁有權的情況下解決環形依賴呢?

答案是不能,當對象之間的擁有權不清楚的時候,weak_ptr並不能帶來幫助。如果存在環,必須要找出來,然后手動打破。那怎么能夠解決環形依賴呢?可以使用有完整垃圾回收機制的語言如Java,Go,Haskell,或者使用有些缺陷的垃圾回收器(C/C++)Boehm GC)

為什么weak_ptr使得控制塊一直使用呢?

我們想下,當要使用weak_ptr來獲取shared_ptr的時候,需要得到指向數據的shared_ptr數目,而這正是通過user-count來得到的,而這塊內存是分配在shared_ptr中的,自然有使用的,那就不會釋放了,即使數據引用數為0了,但是由於make_shared()使得數據和控制塊一起分配,自然只要有weak_ptr指向了控制塊,就不會釋放整塊內存了。

weak_ptr的使用注意

下面有段代碼:

shared_ptr<int> p(new int(5));
weak_ptr<int> q(p);

// some time later

if(int * r = q.get())
{
    // use *r
}

如果在多線程中,在if之后,但是在使用*r之前,另一個線程對p進行了reset,那次后在使用*r則會拋出異常,一個解決方法就是:

shared_ptr<int> p(new int(5));
weak_ptr<int> q(p);

// some time later

if(shared_ptr<int> r = q.lock())
{
    // use *r
}

此時r指向了數據,就不怕被釋放了,因此在使用weak_ptr的時候,應使用lock方法轉換成shared_ptr后使用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM