C++ 的智能指針

專屬所有權(quán):unique_ptr

我們大多數(shù)場景下用到的應(yīng)該都是 unique_ptr。

// C++11
unique_ptr<string> p1;
p1.reste(new string("hello world"));
// C++11
unique_ptr<string> p1(new string("hello world"));
// C++14 
auto w = make_unique<Wight>();  

unique_ptr 代表的是專屬所有權(quán),即由 unique_ptr 管理的內(nèi)存,只能被一個(gè)對象持有,所以,unique_ptr 不支持復(fù)制和賦值,如下:

auto w = std::make_unique<Wight>();
auto w2 = w;  // 編譯錯(cuò)誤

unique_ptr 在默認(rèn)情況下和裸指針的大小是一樣的。

unique_ptr指針本身的生命周期:從unique_ptr指針創(chuàng)建時(shí)開始,直到離開作用域。離開作用域時(shí),若其指向?qū)ο?,則將其所指對象銷毀(默認(rèn)使用delete操作符,用戶可自定義刪除器可指定其他操作)。

在unique_ptr生命周期內(nèi),可以改變unique_ptr所指對象:
1、通過reset方法重新指定、
2、通過release方法釋放所有權(quán)、
3、通過移動語義轉(zhuǎn)移所有權(quán)。

u = nullptr;      // 釋放u指向的對象,并將u置為空
u.release()       // u放棄對指針的控制權(quán),返回指針,并將u置為空
unique_ptr<string> p2(p1.release())    // 將p1置為空,并將資源所有權(quán)轉(zhuǎn)移給p2

u.reset()         // 釋放u指向的對象
u.reset(q)        // 令u釋放原管理的對象,重新管理q資源
p2.reset(p1.release()) 

unique_ptr<string> p2 = std::move(p1);

共享所有權(quán):shared_ptr

代表的是共享所有權(quán),即多個(gè) shared_ptr 可以共享同一塊內(nèi)存,shared_ptr使用引用計(jì)數(shù),每一個(gè)shared_ptr的拷貝都指向相同的內(nèi)存,每使用它一次,內(nèi)部的引用計(jì)數(shù)加1,每析構(gòu)一次,內(nèi)部的引用計(jì)數(shù)減1,減為0時(shí),自動刪除所指向的堆內(nèi)存。

shared_ptr內(nèi)部的引用計(jì)數(shù)是線程安全的,但是對象的讀取需要加鎖。

智能指針是個(gè)模板類,可以指定類型,傳入指針通過構(gòu)造函數(shù)初始化

shared_ptr p(new int(11));

也可以使用make_shared函數(shù)初始化。

auto w = make_shared<Wight>();  

不能將指針直接賦值給一個(gè)shared_ptr指針,一個(gè)是類,一個(gè)是指針。

std::shared_ptr<int> p4 = new int(1)  // 錯(cuò)誤寫法
  • get函數(shù)獲取原始指針,注意不要使用get初始化另一個(gè)智能指針或?yàn)橹悄苤羔樫x值

    shared_ptr<int> p(new int(42));
    
    int *q = p.get();
    shared_ptr<int> p(p.get())    // 錯(cuò)誤 ,原因同下
    shared_ptr<int> p2(q)         // 錯(cuò)誤, 原因同下
    
  • 注意不要用一個(gè)原始指針初始化多個(gè)shared_ptr,否則會造成二次釋放同一內(nèi)存。

    int *p = new int(10);
      
    shared_ptr<int> shp(p);
    shared_ptr<int> shp2(p);      // 兩次釋放同一內(nèi)存
    
  • 注意不要混合使用普通指針和智能指針

    int *x (new int(42));
    process(shared_ptr<int>(x));    // 臨時(shí)變量,內(nèi)存會被釋放掉
    int j = *x;
    
  • 注意避免循環(huán)引用,shared_ptr的一個(gè)最大的陷阱是循環(huán)引用,循環(huán)引用會導(dǎo)致堆內(nèi)存無法正確釋放,導(dǎo)致內(nèi)存泄漏。循環(huán)引用稍后介紹。


weak_ptr的使用

  • weak_ptr是為了配合shared_ptr而引入的一種智能指針,因?yàn)樗痪哂衅胀ㄖ羔樀男袨?,沒有重載operator*和->,它的最大作用在于協(xié)助shared_ptr工作,像旁觀者那樣觀測資源的使用情況。

  • weak_ptr可以從一個(gè)shared_ptr或者另一個(gè)weak_ptr對象構(gòu)造,獲得資源的觀測權(quán)。但weak_ptr沒有共享資源,它的構(gòu)造不會引起指針引用計(jì)數(shù)的增加。使用weak_ptr的成員函數(shù)use_count()可以觀測資源的引用計(jì)數(shù),另一個(gè)成員函數(shù)expired()的功能等價(jià)于use_count()==0,但更快,表示被觀測的資源(也就是shared_ptr的管理的資源)已經(jīng)不復(fù)存在。

    shared_ptr<int> sp = make_shared<int>(10);
    
    wak_ptr<int> w(sp);
    w = sp;
    
  • weak_ptr可以使用一個(gè)非常重要的成員函數(shù)lock()從被觀測的shared_ptr獲得一個(gè)可用的shared_ptr對象, 從而操作資源。但當(dāng)expired()==true的時(shí)候,lock()函數(shù)將返回一個(gè)存儲空指針的shared_ptr。

    if (shared_ptr<int> np = wp.lock()) {
        ......
    }
    
循環(huán)引用

TODO


智能指針與多線程

有兩個(gè)指針p1 和 p2,都指向同一個(gè)堆上的對象object,假設(shè)線程A通過p1指針將對象銷毀了,那p2就成了空懸指針,這時(shí)一種典型的C/C++內(nèi)存錯(cuò)誤。

image.png

下面的例子:展現(xiàn)了這種內(nèi)存錯(cuò)誤。

struct client
{
public:
    client() : m_name(new string("Bob")) { }
    ~client() {  delete m_name;  cout << "~client()" << endl; }
    string *m_name;
};

class client_manager
{
public:
    void and_client(int client_id)
    {
        lock_guard<mutex> lk(m_lock);
        m_client_map.insert({client_id, new client()});
    }

    void remove_client(int client_id)
    {
        lock_guard<mutex> lk(m_lock);
        auto it = m_client_map.find(client_id);
        if (it != m_client_map.end())
        {
            m_client_map.erase(it);
            delete it->second;  // 銷毀
        }
    }

    bool find_client(int client_id, client *&ptr)
    {
        bool result = false;
        lock_guard<mutex> lk(m_lock);
        auto it = m_client_map.find(client_id);
        if (it != m_client_map.end())
        {
            result = true;
            ptr = it->second;
        }

        return result;
    }

private:
    mutex m_lock;
    map<int, client *> m_client_map;
};

int main()
{
    client_manager manager;
    int id = 1;

    thread t1([&manager, &id]() {
        manager.and_client(id++);
    });
    t1.join();

    thread t2([&manager]() {
        this_thread::sleep_for(chrono::seconds(2));  // 模擬OS調(diào)度
        manager.remove_client(1);
    });

    thread t3([&manager]() {
        client *p1 = NULL;
        if (manager.find_client(1, p1))
        {
            this_thread::sleep_for(chrono::seconds(4));  // 模擬OS調(diào)度
            (*p1->m_name) = "world";  // 這里會段錯(cuò)誤,因?yàn)閜1 所指的對象在t2線程已經(jīng)銷毀了。
        }
    });

    t2.join();
    t3.join();
}

所以要想安全的銷毀對象,最好在別人(線程)都看不到的情況下,偷偷的做,這也正是GC的原理,也符合shared_ptr 的 用法。

利用 shared_ptr 改造上面的錯(cuò)誤。

class client_manager
{
public:
    void and_client(int client_id)
    {
        lock_guard<mutex> lk(m_lock);
        shared_ptr<client> p_client(new client());
        m_client_map.insert({client_id, p_client});
    }

    void remove_client(int client_id)
    {
        lock_guard<mutex> lk(m_lock);
        auto it = m_client_map.find(client_id);
        if (it != m_client_map.end())
        {
            m_client_map.erase(it);
        }
    }

    bool find_client(int client_id, shared_ptr<client> &ptr)
    {
        bool result = false;
        lock_guard<mutex> lk(m_lock);
        auto it = m_client_map.find(client_id);
        if (it != m_client_map.end())
        {
            result = true;
            ptr = it->second;
        }

        return result;
    }

private:
    mutex m_lock;
    map<int, shared_ptr<client>> m_client_map;
};

int main()
{
    client_manager manager;
    int id = 1;

    thread t1([&manager, &id]() {
        manager.and_client(id++);
    });
    t1.join();

    thread t2([&manager]() {
        this_thread::sleep_for(chrono::seconds(2)); // 模擬OS調(diào)度
        manager.remove_client(1);
    });

    thread t3([&manager]() {
        shared_ptr<client> p1;
        if (manager.find_client(1, p1))
        {
            cout << p1.use_count() << endl;
            this_thread::sleep_for(chrono::seconds(4)); // 模擬OS調(diào)度
            cout << p1.use_count() << endl;
            (*p1->m_name) = "world";
        }
        else
        {
            cout << "not found" << endl;
        }
    });

    t2.join();
    t3.join();
}

實(shí)現(xiàn)

TODO


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 參考資料:《C++ Primer中文版 第五版》我們知道除了靜態(tài)內(nèi)存和棧內(nèi)存外,每個(gè)程序還有一個(gè)內(nèi)存池,這部分內(nèi)存...
    陳星空閱讀 1,122評論 0 0
  • 智能指針類型 C++98最早的智能指針auto_ptr已被廢止。 C++11/14標(biāo)準(zhǔn)中的unique_ptr、s...
    Levin文學(xué)閱讀 662評論 1 1
  • C++智能指針 原文鏈接:http://blog.csdn.net/xiaohu2022/article/deta...
    小白將閱讀 6,995評論 2 21
  • 導(dǎo)讀## 最近在補(bǔ)看《C++ Primer Plus》第六版,這的確是本好書,其中關(guān)于智能指針的章節(jié)解析的非常清晰...
    小敏紙閱讀 2,086評論 1 12
  • 夜鶯2517閱讀 128,174評論 1 9

友情鏈接更多精彩內(nèi)容