C11內存管理之道:智能指針

一、shared_ptr共享智能指針

  std::shared_ptr使用引用計數,每一個shared_ptr的拷貝都指向相同的內存,在最後一個shared_ptr析構的時候,內存纔會釋放。ios

1.1 基本用法

1.1.1 初始化

  shared_ptr能夠經過make_shared來初始化,也能夠經過shared_ptr<T>輔助函數和reset方法來初始化。智能指針的用法和普通指針的用法相似,不過不須要本身管理分配的內存,對於沒有初始化的指針,只能經過reset來初始化,當智能指針有值,reset會使計數器減1。智能指針能夠經過重載的bool來判斷是否爲空。數組

#include <iostream>
#include <memory>

using namespace std;

int main()
{
    //智能指針初始化
    shared_ptr<int> p = make_shared<int>(20);
    shared_ptr<int> p(new int(1));
    shared_ptr<int> p1 = p;
    shared_ptr<int> ptr;
    
    //所指的對象會被重置,不帶參數則是銷燬
    ptr.reset(new int(5));
    
    if(ptr)
    {
        cout << "ptr is not null" << endl;
    }
    
    return 0;
}

  智能指針不能經過原始指針來初始化:安全

shared_ptr<int> p = new int(1); //編譯報錯,不能直接賦值

1.1.2 獲取原始指針

  當須要獲取原始指針的時候,能夠經過get來返回原始指針。不能釋放,若是釋放會出錯。函數

shared_ptr<int> ptr(new int(1));
int* p = ptr.get();
delete p; //error

1.1.3 指定刪除器

  智能指針支持指定刪除器,在指針引用爲0的時候自動調用。支持普通函數和lambda表達式。this

//普通函數
void DeleteIntPtr(int *p) {delete p;}
shared_ptr<int> p(new int(10), DeleteIntPtr);
//lambda表達式 shared_ptr<int> p(new int(10), [](int *p) {delete p;});

  當智能指針管理動態數組的時候,默認的刪除器不支持數組對象。須要指定刪除器,自定義刪除器或者使用改善的默認修改器均可以。spa

shared_ptr<int> p(new int[10], [](int *p) {delete[] p;}); //lambda
shared_ptr<int> p1(new int[10], default_delete<int []>); //指定delete []

1.2 注意問題

  a.避免一個原始指針初始化多個shared_ptr。指針

int* p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);

  b.不要在參數實參中建立shared_ptr。code

func(shared_ptr<int>(new int), g());

  不一樣的編譯器可能有不一樣的調用約定,若是先new int,而後調用g(),在g()過程當中發生異常,可是shared_ptr沒有建立,那麼int的內存就會泄漏,正確的寫法應該是先建立智能指針。對象

shared_ptr<int> p(new int);
f(p, g());

  c.避免循環使用,循環使用可能致使內存泄漏blog

#include <iostream>
#include <memory>

using namespace std;

struct A;
struct B;

struct A
{
    shared_ptr<B> bptr;
    ~A() { cout << "A is deleted." << endl; }
};

struct B
{
    shared_ptr<A> aptr;
    ~B() { cout << "B is deleted." << endl; }
};

int main()
{
    shared_ptr<A> ap(new A);
    shared_ptr<B> bp(new B);
    
    ap->bptr = bp;
    bp->aptr = ap;
    
    return 0;
}

  這個最經典的循環引用的場景,結果是兩個指針A和B都不會刪除,存在內存泄漏。循環引用致使ap和bp的引用計數爲2,離開做用域以後,ap和bp的引用計數爲1,並不會減0,致使兩個指針都不會析構而產生內存泄漏。

  d.經過shared_from_this()返回this指針。不要將this指針做爲shared_ptr返回出來,由於this指針本質是一個裸指針,這樣可能致使重複析構。

#include <iostream>
#include <memory>

using namespace std;

struct A
{
    shared_ptr<A> GetSelf()
    {
        return shared_ptr<A>(this);
    }
    
    ~A() { cout << "A is deleted." << endl; }
};

int main()
{
    shared_ptr<A> ap(new A);
    shared_ptr<A> ap2 = ap->GetSelf();
    
    return 0;
}

//執行結果
A is deleted.
A is deleted.

  這個例子中,因爲同一指針(this)構造了兩個只能指針ap和ap2,而他們之間是沒有任何關係的,在離開做用域以後this將會被構造的兩個智能指針各自析構,致使重複析構的錯誤。固然,也有解決辦法,解決辦法在以後的weak_ptr介紹。

二、unique_ptr獨佔智能指針

2.1 初始化

  unique_ptr是一個獨佔型智能指針,它不容許其餘的智能指針共享其內部的指針,不容許經過賦值將一個unique_ptr賦值給另外一個unique_ptr。只能經過函數來返回給其它的unique_ptr,好比move函數,可是轉移以後,再也不對以前的指針具備全部權。

unique_ptr<int> uptr(new int(10));
unique_ptr<int> uptr2 = uptr;       //error
unique_ptr<int> uptr3 = move(uptr); //uptr將變爲null

2.2 特色

2.2.1 數組

  unique_ptr和shared_ptr相比除了獨佔以外,unique_ptr還能夠指向一個數組。

unique_ptr<int []> ptr(new int[10]);   //ok
ptrp[1] = 10;

shared_ptr<int []> ptr2(new int[10]); //error

2.2.2 刪除器

  unique_ptr必須指定刪除器類型,不像shared_ptr那樣直接指定刪除器。

shared_ptr<int> ptr(new int(1), [](int *p){delete p;});           //ok
unique_ptr<int> ptr2(new int(1), [](int *p){delete p;});           //error
unique_ptr<int, void(*)(int *)> ptr2(new int(1), [](int *p){delete p;}); //ok

  經過指定函數類型,而後經過lambda表達式實現是能夠,可是若是捕獲了變量將會編譯報錯,由於lambda表達式在沒有捕獲變量的狀況下能夠直接轉換爲函數指針,可是捕獲了變量就沒法轉換。若是要支持,能夠經過std::function來解決。

unique_ptr<int, void(*)(int *)> ptr2(new int(1), [&](int *p){delete p;});            //error
unique_ptr<int, std::function<void(int*)>> ptr2(new int(1), [&](int *p){delete p;}); //ok

  unique_ptr支持自定義刪除器。

#include <iostream>
#include <memory>
#include <functional>

using namespace std;

struct DeleteUPtr
{
    void operator()(int* p)
    {
        cout << "delete" << endl;
        delete p;
    }
};

int main()
{
    unique_ptr<int, DeleteUPtr> p(new int(1));
    
    return 0;
}

三、weak_ptr弱引用智能指針

弱引用智能指針weak_ptr用來監視shared_ptr,不會使引用技術加1,也無論理shared_ptr內部的指針,主要是監視shared_ptr的生命週期。weak_ptr不共享指針,不能操做資源,它的構造和析構都不會改變引用計數。

3.1 基本用法

3.1.1 觀測計數

  經過use_count()方法來得到當前資源的引用計數。

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);

cout << wp.use_count() << endl; //輸出1

3.1.2 觀察是否有效

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);

if(wp.expired())
{
    cout << "sp 已經釋放,無效" << endl;
}
else
{
    cout << "sp 有效" << endl;
}

3.1.3 監視

  能夠經過lock方法來獲取所監視的shared_ptr。

#include <iostream>
#include <memory>

using namespace std;

weak_ptr<int> gw;

void f()
{
    //監聽是否釋放
    if(gw.expired()) 
    {
        cout << "gw is expired." << endl;
    }
    else
    {
        auto spt = gw.lock();
        cout << *spt << endl;
    }
}

int main()
{
    {
        auto p = make_shared<int>(20);
        gw = p;
        f();
    }
    f();
    
    return 0;
}

//執行結果
20
gw is expired.

3.2 返回this指針

  sharerd_ptr不能直接返回this指針,須要經過派生std::enable_shared_from_this類,並經過其方法shared_from_this來返回智能指針,由於std::enable_shared_from_this類中有一個weak_ptr,這個weak_ptr用來觀測this指針,調用shared_from_this方法時,調用了內部的weak_ptr的lock()方法,將所觀測的sharerd_ptr返回。

#include <iostream>
#include <memory>

using namespace std;

struct A:public enable_shared_from_this<A>
{
    shared_ptr<A> GetSelf()
    {
        return shared_from_this();
    }
    
    ~A()
    {
        cout << "A is deleted." << endl;
    }
};

int main()
{
    shared_ptr<A> spy(new A);
    shared_ptr<A> p = spy->GetSelf(); //ok
    
    return 0;
}

//執行結果
A is deleted.

  在外面建立A對象的智能指針經過該對象返回this的智能指針是安全的,由於shared_from_this()是內部weak_ptr調用lock()方法以後返回的智能指針,在離開做用域以後,spy的引用計數爲0,A對象會被析構,不會出現A對象被析構兩次的問題。

  須要注意的是,獲取自身智能指針的函數僅在share_ptr<T>的構造函數調用以後才能使用,由於enable_shared_from_this內部的weak_ptr只有經過shared_ptr才能構造。

3.3 解決循環引用問題

  shared_ptr的循環引用可能致使內存泄漏,以前的例子再也不贅述,經過weak_ptr能夠解決這個問題,怎麼解決呢?答案是,將A或者B任意一個成員變量改成weak_ptr便可。

#include <iostream>
#include <memory>

using namespace std;

struct A;
struct B;

struct A
{
    shared_ptr<B> bptr;
    ~A() { cout << "A is deleted." << endl; }
};

struct B
{
    weak_ptr<A> aptr;
    ~B() { cout << "B is deleted." << endl; }
};

int main()
{
    shared_ptr<A> ap(new A);
    shared_ptr<B> bp(new B);
    
    ap->bptr = bp;
    bp->aptr = ap;
    
    return 0;
}

//執行結果
A is deleted.
B is deleted.

  這樣在對B成員賦值時,即bp->aptr = ap,因爲aptr是weak_ptr,並不會增長引用計數,因此ap的計數仍然是1,在離開做用域以後,ap的引用計數會減爲0,A指針會被析構,析構以後,其內部的bptr引用計數會減1,而後離開做用域以後,bp引用計數從1減爲0,B對象也被析構,因此不會發生內存泄漏。

相關文章
相關標籤/搜索