智能指針之 weak_ptr

来源:https://www.cnblogs.com/blog-yejy/archive/2018/09/30/9727070.html
-Advertisement-
Play Games

1. weak_ptr 介紹 std::weak_ptr 是一種智能指針,它對被 std::shared_ptr 管理的對象存在非擁有性("弱")引用。在訪問所引用的對象指針前必須先轉換為 std::shared_ptr。 主要用來表示臨時所有權,當某個對象存在時才需要被訪問。轉換為shared_p ...


1. weak_ptr 介紹

std::weak_ptr 是一種智能指針,它對被 std::shared_ptr 管理的對象存在非擁有性("弱")引用。在訪問所引用的對象指針前必須先轉換為 std::shared_ptr。 主要用來表示臨時所有權,當某個對象存在時才需要被訪問。轉換為shared_ptr的過程等於對象的shared_ptr 的引用計數加一,因此如果你使用weak_ptr獲得所有權的過程中,原來的shared_ptr被銷毀,則該對象的生命期會被延長至這個臨時的 std::shared_ptr 被銷毀為止。 weak_ptr還可以避免 std::shared_ptr 的迴圈引用

std::weak_ptr簡單使用:(編譯系統:Linux centos 7.0 x86_64 編譯器:gcc 4.8.5 )

#include <memory>
#include <iostream>

class foo
{
public:
    foo()
    {
        std::cout << "foo construct.." << std::endl;
    }

    void method()
    {
        std::cout << "welcome Test foo.." << std::endl;
    }

    ~foo()
    {
        std::cout << "foo destruct.." << std::endl;
    }
};

int main()
{
    // weak_ptr
    foo* foo2 = new foo();

    // share_ptr 管理對象
    std::shared_ptr<foo> shptr_foo2(foo2);

    // weak_ptr 弱引用
    std::weak_ptr<foo> weak_foo2(shptr_foo2);

    // 如果要獲取數據指針,需要通過lock介面獲取
    weak_foo2.lock()->method();

    std::shared_ptr<foo> tmp =  weak_foo2.lock();

    // 我們這邊有嘗試多次獲取所有權(lock),看一下引用計數個數
    std::cout << "shptr_foo2 RefCount: " << weak_foo2.lock().use_count() << std::endl;

    return 0;
}

執行結果:

bash-4.2$ ./share_ptr 
foo construct..
welcome Test foo..
shptr_foo2 RefCount: 3
foo destruct..

我們可以看到,weak_ptr多次通過lock轉換成shared_ptr,獲得shared_ptr後可以成功調用管理對象的方法,這個過程中引用計數是在增加的,因此如果原來的shared_ptr銷毀是不影響你這個臨時對象使用, 資源析構正常。 下麵是gnu STL 庫中最後調用lock函數會跑到的地方,引用計數 + 1 。(GNU STL 文件:shared_ptr_base.h)

2. weak_ptr 實現和迴圈引用問題

1. shared_ptr 迴圈引用問題

我們首先看一下迴圈引用的問題,具體代碼如下:

測試類:

#include <memory>
#include <iostream>

class foo;
class Test
{
public:
    Test()
    {
        std::cout << "construct.." << std::endl;
    }

    void method()
    {
        std::cout << "welcome Test.." << std::endl;
    }

    ~Test()
    {
        std::cout << "destruct.." << std::endl;
    }

public:
    std::shared_ptr<foo> fooptr;
};

class foo
{
public:
    foo()
    {
        std::cout << "foo construct.." << std::endl;
    }

    void method()
    {
        std::cout << "welcome Test foo.." << std::endl;
    }

    ~foo()
    {
        std::cout << "foo destruct.." << std::endl;
    }

public:
   std::shared_ptr<Test> testptr;
};

main函數:

int main()
{
    // 迴圈引用 測試
    Test* t2 = new Test();
    foo* foo1 = new foo();

    std::shared_ptr<Test> shptr_Test(t2);
    std::shared_ptr<foo>  shptr_foo(foo1);

    std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;
    std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;

    shptr_Test->fooptr = shptr_foo;
    shptr_foo->testptr = shptr_Test;
      
    std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;
    std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;

    return 0;
}

測試結果:

bash-4.2$ ./share_ptr 
construct..
foo construct..
shptr_Test RefCount: 1
shptr_foo RefCount: 1
shptr_Test RefCount: 2
shptr_foo RefCount: 2

從列印結果可以很明顯的看出,經過迴圈引用, 對象引用計數變成了2,並且執行完後,資源沒有釋放,沒有調用類的destruct(析構)函數

將類中的std::shared_ptr 換成 std::weak_ptr

class foo;
class Test
{
public:
    Test()
    {
        std::cout << "construct.." << std::endl;
    }

    void method()
    {
        std::cout << "welcome Test.." << std::endl;
    }

    ~Test()
    {
        std::cout << "destruct.." << std::endl;
    }

public:
    std::weak_ptr<foo> fooptr;
};

class foo
{
public:
    foo()
    {
        std::cout << "foo construct.." << std::endl;
    }

    void method()
    {
        std::cout << "welcome Test foo.." << std::endl;
    }

    ~foo()
    {
        std::cout << "foo destruct.." << std::endl;
    }

public:
    std::weak_ptr<Test> testptr;
};

再看下weak_ptr的執行結果,可以看到計數正常,資源成功釋放

bash-4.2$ ./share_ptr 
construct..
foo construct..
shptr_Test RefCount: 1
shptr_foo RefCount: 1
shptr_Test RefCount: 1
shptr_foo RefCount: 1
foo destruct..
destruct..

2. weak_ptr 實現

我們這邊貼一下weak_ptr類的代碼:

template <class T>
class weak_ptr
{
public:
    template <class S>
    friend class weak_ptr;

    template <class S>
    friend class shared_ptr;

    constexpr weak_ptr() noexcept : m_iWeakRefCount(nullptr), m_ptr(nullptr) { }

    weak_ptr( const weak_ptr<T>& rhs ) noexcept : m_iWeakRefCount(rhs.m_iWeakRefCount)
    {
        m_ptr = rhs.lock().getPointer();
    }
 
    weak_ptr( const shared_ptr<T>& rhs ) noexcept
     : m_iWeakRefCount(rhs.m_iRefCount), m_ptr(rhs.m_ptr) { }

    template <typename S>
    weak_ptr & operator=(const shared_ptr<S> & rhs)
    {
        m_ptr = dynamic_cast<T *>(rhs.m_ptr);
        m_iWeakRefCount = rhs.m_iRefCount;
        return *this;
    }

    template <typename S>
    weak_ptr & operator=(const weak_ptr<S> & rhs)
    {
        m_ptr = dynamic_cast<T *>(rhs.m_ptr);
        m_iWeakRefCount = rhs.m_iWeakRefCount;
        return *this;
    }

    weak_ptr& operator=( const weak_ptr& rhs ) noexcept
    {
        m_iWeakRefCount = rhs.m_iWeakRefCount;
        m_ptr = rhs.m_ptr;

        return *this;
    }

    weak_ptr& operator=( const shared_ptr<T>& rhs ) noexcept
    {
        m_iWeakRefCount = rhs.m_iRefCount;
        m_ptr = rhs.m_ptr;

        return *this;
    }

    shared_ptr<T> lock() const noexcept
    {
        shared_ptr<T> tmp;
        if(m_iWeakRefCount && *m_iWeakRefCount > 0)
        {
            tmp.m_iRefCount = m_iWeakRefCount;
            tmp.m_ptr = m_ptr;

            if(tmp.m_iRefCount)
            {
                ++(*tmp.m_iRefCount);
            }
        }

        return tmp;
    }

    int use_count()
    {
        return *m_iWeakRefCount;
    }

    bool expired() const noexcept
    { 
        return *m_iWeakRefCount == 0;
    }

    void Reset()
    {
        m_ptr = NULL;
        m_iWeakRefCount = NULL;
    }

private:
    int * m_iWeakRefCount; 

    T* m_ptr;
};

主要註意的是lock函數,如果計數指針為空,那麼會返回一個空的shared_ptr,然後就是不能重載operator*和operator-> 操作符

主要參考: cppreference.com
完整實現見:smart_ptr

3. enable_shared_from_this

這邊還有一個點也要介紹一下,那就是enable_shared_from_this,這個主要是為了處理在shared_ptr管理的對象中要使用該對象的指針所引出的問題。 我們看下下麵這個例子:

class foo
{
public: 
    std::shared_ptr<foo> getptr() 
    {       
        //  如果類中要返回自己的指針怎麼辦?  
        return std::shared_ptr<foo>(this);  
    }   

    ~foo() 
    { 
        std::cout << "foo destruct .. " << std::endl; 
    }
};

int main()
{
    std::shared_ptr<foo> bp1(new foo());
    bp1->getptr();  
    std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
}

看下結果,釋放兩次

ash-4.2$ ./share_ptr 
foo destruct .. 
bp1.use_count() = 1
foo destruct .. 

其實我們都不用測試,因為你如果直接使用該對象的this指針又拷貝給另一個shared_ptr,那不就等於兩個沒有關係的shared_ptr管理同一個對象了嗎? 釋放的時候等於會調用兩次該對象的析構函數。enable_shared_from_this就是用來解決這個問題的。看下代碼:

class foo : public std::enable_shared_from_this<foo>
{
public: 
    std::shared_ptr<foo> getptr() 
    {         
        return shared_from_this();  
    }   

    ~foo() 
    { 
        std::cout << "foo destruct .. " << std::endl; 
    }
};

int main()
{
    std::shared_ptr<foo> bp1(new foo());
    bp1->getptr();  
    std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
}

看下結果,成功釋放:

bash-4.2$ ./share_ptr 
bp1.use_count() = 1
foo destruct .. 

總結一下,weak_ptr本質是以一種觀察者的形象存在,它可以獲取到觀察主體的狀態,但是無法獲取直接獲取到觀察主體,無法直接對觀察主體修改,無法釋放觀察主體的資源,你只能通過轉換成shared_ptr來做一些事情。 和觀察者模式很像,訂閱,得到觀察主體狀態,在多線程環境下會比較管用!

2018年9月30日00:40:02


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 實習生張大胖這是個代碼寫得很爛的電商系統,只要運行一段時間,伺服器就會出現Out Of Memory。別人都忙得四腳朝天,於是實習生張大胖被抓了壯丁去研究為什麼會出現OOM。剛入行的張大胖技術水平一般,“裝模作樣”地看代碼,研究日誌,請教老員工,一個星期過去了,還是一無所獲。周一例行的項目會議上, ...
  • HTTPS 站中的幾大難題 性能,包括: 其次,相容性及周邊,如: 如何解決 採用了統一接入層的架構,並配備管控平臺。這樣的設計解決了很多問題,比如證書分散且落地不安全、軟體版本難以維護、配置過多、難以標準和自動化、VIP 過多等; 以功能變數名稱收斂的方式減少建連; 採用 HSTS 技術去掉 80 到 4 ...
  • Python環境搭建首先到官網(www.python.org)下載相應的安裝版本。主要分為windows和Linux兩種: 一、windows環境 下載地址:https://www.python.org/downloads/windows/ 1、下載好版本後按照正常提示安裝。 2、設置環境變數 py ...
  • django下載Excel,使用django-excel插件 由於目前的資料多是使用pandas或xlwt庫實現的。其實沒有那麼的麻煩,因為django有相對應的插件django-excel。 該插件是依賴於pyexcel庫寫的。不過,不用專門安裝pyexcel庫,因為在安裝django-excel ...
  • 環境 一、websocket協議 1. 先建立連接 wss://broadcastlv.chat.bilibili.com/sub 2. 發送登錄包 { "uid": 0表示未登錄,否則為用戶ID, "roomid": 房間ID, "protover": 1, "platform": "web", ...
  • 下麵的程式會發生崩潰: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 include <stdio.h> include <iostream> using namespace std; int main(void) { int p; int i = ...
  • 單變數:表達式、方程式、函數或者一元多項式等 數據:http://www.presidency.ucsb.edu/data/sourequests.php美國總統歷年在國情咨文中對國會提起的訴求數量 一、獲取數據 本次使用到的數據量並不多,不過還是按照常規思路,通過爬蟲獲取。 得到的數據: 二、繪製 ...
  • n1、下載windows版本的nginx安裝包 nginx官網,我使用的是穩定版的1.8.1 2、下載好的安裝包,找一個路徑進行解壓(註意:不要使用中文路徑);解壓之後nginx就安裝好了,嘻嘻window下安裝特別簡單,比linux簡單多了 3、然後就需要配置tomcat伺服器了,因為要模擬集群的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...