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


1、shared_ptr共享智能指針

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

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表達式。

//普通函數
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;});

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

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。

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

  不同的編譯器可能有不同的調用約定,如果先new int,然后調用g(),在g()過程中發生異常,但是shared_ptr沒有創建,那么int的內存就會泄漏,正確的寫法應該是先創建智能指針。

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

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

#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介紹。

2、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;
}

3、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對象也被析構,所以不會發生內存泄漏。


免責聲明!

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



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