std::weak_ptr<void>綁定到std::shared_ptr<T>

来源:https://www.cnblogs.com/lycpp/archive/2022/11/03/16854177.html
-Advertisement-
Play Games

前言 我們在啟動 Spring Boot 項目時,控制台會列印出 Spring Boot 專屬的標語,也稱 banner(橫幅標語/廣告),效果如下: 實際上,上面這個 banner,我們可以自定義,而很多公司也有使用自己的 banner 的。 下麵介紹在 Spring Boot 項目中使用自定義 ...


最近在忙自己的研究生科研工作和儘量在不看源碼的情況下寫一個玩具版的muduo(我已經看過陳碩的《Linux多線程服務端編程:使用muduo C++網路庫》,相當於按自己的理解再寫一遍),沒太有時間寫C++對象模型的後面部分,等組會開完後再繼續寫。
今天就寫一下幾天前看到的一個小技巧,也即標題:std::weak_ptr<void>綁定到std::shared_ptr<T>

std::weak_ptr

我們知道weak_ptr目的是防止只使用std::shared_ptr導致的迴圈引用,從而導致記憶體泄漏。一個經典的例子如下:

#include <iostream>
#include <vector>
#include <memory>
#include <string>

class Child;

class Parent {
public:
    Parent(const std::string& name)
        : m_name(name),
          m_children()
    {}
    ~Parent();

    void addChild(std::shared_ptr<Child>& child) {
        m_children.push_back(child);
    }

    const std::string&
    getName() const {
        return m_name;
    }

    std::vector<std::shared_ptr<Child>>&
    getChildren() {
        return m_children;
    }
private:
    std::string m_name;
    std::vector<std::shared_ptr<Child>> m_children; // Parent對象使用shared_ptr來持有Child對象
};

class Child {
public:
    Child(const std::string& name, std::shared_ptr<Parent>& parent)
        : m_name(name),
          m_parent(parent)
    {}

    ~Child() {
        std::cout << m_name << "'s destruction" << std::endl;
    }

    void showParentName() const {
        std::shared_ptr<Parent> parent = m_parent.lock();
        if (parent) {
            std::cout << m_name << "'s parent: " << parent->getName() << std::endl;
        } else {
            std::cout << m_name << "'s parent has destructed" << std::endl;
        }
    }

private:
    std::string m_name;
    std::weak_ptr<Parent> m_parent; // Child對象使用weak_ptr來引用Parent對象
};

Parent::~Parent() {
    std::cout << m_name << "'s destruction" << std::endl;
}

void func() {
    std::shared_ptr<Parent> parent = std::make_shared<Parent>("Parent01");
    std::shared_ptr<Child> child = std::make_shared<Child>("Child01", parent);
    parent->addChild(child);
    child->showParentName();
}

int main() {
    func();
}

// Output:
//  Child01's parent: Parent01
//  Parent01's destruction
//  Child01's destruction

我們可以看到ParentChild對象均正常析構了。

std::weak_ptr與其綁定的std::shared_ptr

在上面的代碼中,如果有其他地方持有std::shared_ptr<Child>,那麼在Parent析構時,被該std::share_ptr<Child>持有的Child對象不會析構,而且Child::showParentName會正常識別出其Parent對象已經被析構。這就是std::weak_ptr能判斷其綁定的std::shared_ptr管理的對象是否已經析構。
但有一個問題,如果我只是用std::weak_ptr來判斷其綁定的std::shared_ptr管理的對象是否已經析構,但其綁定的std::shared_ptr管理的對象類型不一定怎麼辦?正如標題所言,std::weak_ptr<void>可以綁定到所有類型的std::shared_ptr,所以只要使用一個std::weak_ptr即可。
我知道這個用法的來源是陳碩的muduo網路庫
muduo中,類Channel用於管理一個socket描述符的讀、寫、出錯事件,並調用相應的回調。
但有一個問題是Channel類並不持有該socket描述符(只存有該socket描述符,但其生命期並不歸Channel管理),那如何判斷Channel對應的管理socket描述符的類是否已經析構呢(因為Channel的讀寫出錯回調往往是通過std::bind或者lambda包裹的socket描述符的持有者的private方法,如果持有者已經析構,再調用回調會導致段錯誤從而core dump)?
muduo就是在Channel中使用std::weak_ptr<void>。其有一個方法Channel::tie,接受const std::shared_ptr<void>&類型的參數,此參數要求傳入持有socket描述符管理者對象的std::shared_ptrmuduo將此參數賦值給給std::weak_ptr<void>對象,使其可以監控socket描述符管理者對象是否已經析構。部分代碼如下:

// muduo/net/Channel.cc

void Channel::tie(const std::shared_ptr<void>& obj)
{
  tie_ = obj;   // std::weak_ptr<void> tie_
  tied_ = true; // bool tied_
}

void Channel::handleEvent(Timestamp receiveTime)
{
  std::shared_ptr<void> guard;
  if (tied_)
  {
    guard = tie_.lock();
    if (guard)
    {
      handleEventWithGuard(receiveTime);
    }
  }
  else
  {
    handleEventWithGuard(receiveTime);
  }
}

這樣的用法是合法的嗎?我們可以在cppreference上查看一下std::shared_ptr和std::weak_ptr的相關信息。
可以看到std::shared_ptr有如下的構造函數:

// https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
template<typename T>    // 這兩行是我自己加的,
class std::shared_ptr { // 說明裡面是該類的成員函數
// ...  (1) - (2)
template< class Y >
explicit shared_ptr( Y* ptr ); // (3)

// ... (4)-(13)
};

可以看到可以由std::shared_ptr<Y>構造std::shared_ptr<T>,要求是:

For (3-4,6), Y* must be convertible to T*. // until C++17

也就是只要Y*能轉化為T*即可,而一般的指針類型(除了成員指針和成員函數指針)都可以轉化為void*,所以std::shared_ptr<T>構造std::shared_ptr<void>是可以的,而且他們管理著相同的對象。測試如下:

#include <iostream>
#include <memory>

class Test {
public:
    ~Test() {
        std::cout << "Test::~Test()" << std::endl;
    }
};

int main() {
    std::shared_ptr<void> pvoid;
    {
        std::shared_ptr<Test> pTest = std::make_shared<Test>();
        pvoid = pTest;
    }
    std::cout << "pTest has destructed" << std::endl;
}

// Output:
//  pTest has destructed
//  Test::~Test()

然後std::shared_ptr<void>構造std::weak_ptr<void>就是理所當然的了。
那能不能由std::shared_ptr<T>直接構造std::weak_ptr<void>呢?按理來說是可以的,我們在cppreference裡面找一下可以發現:

// https://en.cppreference.com/w/cpp/memory/weak_ptr/weak_ptr
template<T>      // 這兩行是我自己加的,
std::weak_ptr {  // 說明裡面是該類的成員函數
    template< class Y >
    weak_ptr( const std::shared_ptr<Y>& r ) noexcept;
};

The templated overloads don't participate in the overload resolution unless Y* is implicitly convertible to T*

即如果Y*能隱式轉化為T*的話是可以的,而我們知道一般的指針類型(除了成員指針和成員函數指針)都可以隱式轉化為void*類型,所以由std::share_ptr<T>構造std::weak_ptr<void>是可行的。驗證如下:

#include <iostream>
#include <memory>

class Test {
public:
    ~Test() {
        std::cout << "Test::~Test()" << std::endl;
    }
};

std::weak_ptr<void> func() {
    std::shared_ptr<Test> pTest = std::make_shared<Test>();
    std::weak_ptr<void> pVoidWeak = pTest;
    std::shared_ptr<void> pVoid = pVoidWeak.lock();
    if (pVoid) {
        std::cout << "Test object exists" << std::endl;
    } else {
        std::cout << "Test object has been destructed" << std::endl;
    }
    return pVoidWeak;
}

int main() {
    auto pVoidWeak = func();
    std::shared_ptr<void> pVoid = pVoidWeak.lock();
    if (pVoid) {
        std::cout << "Test object exists" << std::endl;
    } else {
        std::cout << "Test object has been destructed" << std::endl;
    }
}

// Output:
//  Test object exists
//  Test::~Test()
//  Test object has been destructed

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

-Advertisement-
Play Games
更多相關文章
  • 簡介: 適配器模式屬於結構型設計模式。 將一個類的介面轉換成可應用的相容介面。適配器使原本由於介面不相容而不能一起工作的那些類可以一起工作。 適配器模式有兩種實現方案,一種是繼承的方式,一種是組合的方式。 適用場景: 相容不方便更改的“祖傳”代碼。 歸納具有相似點的模塊,比如Laravel File ...
  • 前言 所謂熱部署,簡單來說,就是代碼修改後不需重啟項目就可自動載入出新的內容。 ==註意:熱部署在 debug 調試模式下才生效!== IDEA 配置 在 IDE(IDEA)中開啟相關項目自動構建選項 開啟編譯器設置中修改後自動編譯的選項(下圖是 IDEA 2021版本,其他版本可能在其他位置) S ...
  • 作者:張富春(ahfuzhang),轉載時請註明作者和引用鏈接,謝謝! cnblogs博客 zhihu Github 公眾號:一本正經的瞎扯 團隊中之前的文件下載做得比較複雜,因為擔心量太大,是後臺做非同步的下載,最終生成文件,傳送文件到CDN伺服器,最後再告訴用戶下載鏈接。 其實在查詢介面中就可以實 ...
  • 1 Spring boot源碼環境構建 推薦環境: idea:2020.3 gradle:版本gradle-6.5.1 jdk:1.8 註意!idea和gradle的版本有相容性問題,要註意搭配 1.1 Spring boot源碼下載 1、從github獲取源碼,網址: https://github ...
  • Altair是Python統計可視化庫,提供了強大而簡潔的可視化語法,可以產出漂亮的數據分析可視化結果,並支持互動式操作和勾選局部數據深入分析。本文以實例講解Altair的數據分析過程,以及交互文檔報告的生成。... ...
  • json.loads(),json.dumps(): 用來處理數據格式(json <==> python) json.load(),json.dump(): 用於文件操作(讀、寫) ...
  • 什麼是變數 我們只要與生活中的數學做類型就可以清楚的瞭解什麼是變數 在Python中,變數的概念基本上和初中代數的方程變數是一致的。例如,對於方程式 y=x*x ,x就是變數。當x=2時,計算結果是4,當x=5時,計算結果是25 合法的變數名 我們在學習電腦程式過程中,變數不僅可以是數字,還可以是 ...
  • 前言 嗨嘍,大家好呀~這裡是愛看美女的茜茜吶 又到了學Python時刻~ 開發環境 & 第三方模塊: 解釋器版本: python 3.8 代碼編輯器: pycharm 2021.2 requests: pip install requests pyecharts: pip install pyech ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...