C++11:shared_ptr的設計哲學(原理+源碼):內存安全和性能的架構權衡

0.簡介

在C++編程世界中,內存管理是一把雙刃劍,手動管理帶來了極致的內存控制能力,但也帶來了像內存泄漏,野指針等問題;自動垃圾回收雖然安全,但卻會帶來一定的性能損耗。本文將介紹C++11引入shared_ptr,通過對其原理和源碼的解析,來了解其對于內存安全和性能的權衡。

1.原理

要了解一個設計,首先要看這個設計要解決的問題,shared_ptr的核心目標是實現安全的動態內存管理,所以內存安全就是其首要的任務;但也不能只考慮安全,也需要對像性能,易用性進行一些考慮,下面就在這幾個方面對shared_ptr的設計原理進行分析。

1.1 安全性設計

1)動態管理:分配控制的RAII實現,通過重載各種操作符來實現引用計數的增減和內存釋放。
2)線程安全:通過原子變量以及內存順序來保證線程安全。
3)釋放安全:可以自定義釋放使用的Deleter來進行釋放,可以利用這個特性做退出作用域的釋放,下面源碼分析會介紹。

1.2 性能設計

要看性能的設計首先要明確可能導致性能問題的點,第一個就是并發場景下引用計數的共享;然后就是shared_ptr本身控制塊的內存分配。
1)對于并發場景下的引用計數,可以看源碼解析中的三種枚舉,通過默認的原子操作,盡可能的降低影響。
2)對于內存分配,通過提供make_shared來實現一次性的分配,不多次申請內存。
在這里插入圖片描述

1.3 易用性設計

易用性可以從以下方面進行考慮:
1)支持的類型:通過模板來支持各種類型。
2)創建方式:通過提供make_shared來支持更高效的創建方式。
3)使用方式:通過提供與裸指針一致的使用方式來降低使用要求。
4)和外部的集成:提供和標準庫良好的集成。

2.源碼解析

源碼分析我們先看其數據成員,然后看其主要的函數以及基于其函數我們可以得到的用法。

2.1 數據成員

shared_ptr繼承自__shared_ptr,其主要的成員也在__shared_ptr,其成員如下:

element_type*    _M_ptr;         // Contained pointer.
__shared_count<_Lp>  _M_refcount;    // Reference counter.

可以看到其中一個是傳入的指針,另外一個是引用計數。傳入的指針含義和實現比較明確,我們來看引用計數部分,首先引用計數包含兩個部分,一個是__shared_count的模板類,一個是_LP,我們一個個來看,先來看__shared_count,其是引用計數的核心類,其內成員主要是:

_Sp_counted_base<_Lp>*  _M_pi;

通過_M_pi的下面兩個函數實現引用計數的加減,其內部封裝原子的操作:

void_M_weak_add_ref() noexcept{ __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1); }void_M_weak_release() noexcept{// Be race-detector-friendly. For more info see bits/c++config._GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1){_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);if (_Mutex_base<_Lp>::_S_need_barriers){// See _M_release(),// destroy() must observe results of dispose()__atomic_thread_fence (__ATOMIC_ACQ_REL);}_M_destroy();}}

接下來來看_LP,其類型如下:

// Available locking policies:// _S_single    single-threaded code that doesn't need to be locked.// _S_mutex     multi-threaded code that requires additional support//              from gthr.h or abstraction layers in concurrence.h.// _S_atomic    multi-threaded code using atomic operations.enum _Lock_policy { _S_single, _S_mutex, _S_atomic }; 

默認策略如下:

static const _Lock_policy __default_lock_policy = 
#ifndef __GTHREADS_S_single;
#elif defined _GLIBCXX_HAVE_ATOMIC_LOCK_POLICY_S_atomic;
#else_S_mutex;
#endif

其決定是否需要內存屏障,通過特化模板實現:

template<_Lock_policy _Lp>class _Mutex_base{protected:// The atomic policy uses fully-fenced builtins, single doesn't care.enum { _S_need_barriers = 0 };};template<>class _Mutex_base<_S_mutex>: public __gnu_cxx::__mutex{protected:// This policy is used when atomic builtins are not available.// The replacement atomic operations might not have the necessary// memory barriers.enum { _S_need_barriers = 1 };};

2.2 關鍵函數和利用方式

關鍵的函數我們來看一些操作符重載以及自定義刪除和內存分配器的函數。
1)一些賦值的重載,讓其可控。

    __shared_ptr&operator=(__shared_ptr&& __r) noexcept{__shared_ptr(std::move(__r)).swap(*this);return *this;}template<class _Yp>_Assignable<_Yp>operator=(__shared_ptr<_Yp, _Lp>&& __r) noexcept{__shared_ptr(std::move(__r)).swap(*this);return *this;}template<typename _Yp, typename _Del>_UniqAssignable<_Yp, _Del>operator=(unique_ptr<_Yp, _Del>&& __r){__shared_ptr(std::move(__r)).swap(*this);return *this;}voidreset() noexcept{ __shared_ptr().swap(*this); }

2)自定義內存分配器和釋放操作,其中自定義內存分配器直接按照接口定義就可以,較為常用的是自定義釋放,可以用來做退出作用域的操作。

template<typename _Yp, typename _Deleter,typename = _Constructible<_Yp*, _Deleter>>shared_ptr(_Yp* __p, _Deleter __d): __shared_ptr<_Tp>(__p, std::move(__d)) { }/***  @brief  Construct a %shared_ptr that owns a null pointer*          and the deleter @a __d.*  @param  __p  A null pointer constant.*  @param  __d  A deleter.*  @post   use_count() == 1 && get() == __p*  @throw  std::bad_alloc, in which case @a __d(__p) is called.**  Requirements: _Deleter's copy constructor and destructor must*  not throw**  The last owner will call __d(__p)*/template<typename _Deleter>shared_ptr(nullptr_t __p, _Deleter __d): __shared_ptr<_Tp>(__p, std::move(__d)) { }/***  @brief  Construct a %shared_ptr that owns the pointer @a __p*          and the deleter @a __d.*  @param  __p  A pointer.*  @param  __d  A deleter.*  @param  __a  An allocator.*  @post   use_count() == 1 && get() == __p*  @throw  std::bad_alloc, in which case @a __d(__p) is called.**  Requirements: _Deleter's copy constructor and destructor must*  not throw _Alloc's copy constructor and destructor must not*  throw.**  __shared_ptr will release __p by calling __d(__p)*/template<typename _Yp, typename _Deleter, typename _Alloc,typename = _Constructible<_Yp*, _Deleter, _Alloc>>shared_ptr(_Yp* __p, _Deleter __d, _Alloc __a): __shared_ptr<_Tp>(__p, std::move(__d), std::move(__a)) { }/***  @brief  Construct a %shared_ptr that owns a null pointer*          and the deleter @a __d.*  @param  __p  A null pointer constant.*  @param  __d  A deleter.*  @param  __a  An allocator.*  @post   use_count() == 1 && get() == __p*  @throw  std::bad_alloc, in which case @a __d(__p) is called.**  Requirements: _Deleter's copy constructor and destructor must*  not throw _Alloc's copy constructor and destructor must not*  throw.**  The last owner will call __d(__p)*/template<typename _Deleter, typename _Alloc>shared_ptr(nullptr_t __p, _Deleter __d, _Alloc __a): __shared_ptr<_Tp>(__p, std::move(__d), std::move(__a)) { }

自定義釋放操作可以按照如下方式使用:

void test()
{int *pData = new(std::nothrow) int(10);std::shared_ptr scope_exit(nullptr,[&](void*){if(nullptr != pData)delete pData;});
}

2.3 問題和處理

shared_ptr會存在循環引用問題,這個可以使用weak_ptr解決,后面會專門對weak_ptr的實現原理和源碼進行分析。

3.總結

對于shared_ptr的使用,我們要知道它帶來的便利和問題。在開發領域沒有銀彈,只有取舍,也就是優秀的架構不是選擇完美的工具,而是理解每種工具的代價。所以在一些高性能關鍵領域可以不去使用shared_ptr,而一些常規領域建議使用。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/87718.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/87718.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/87718.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Mysql EXPLAIN 執行計劃

EXPLAIN SELECT SQl。。。。界面filtered儲引擎返回的數據在經過服務器層 WHERE 條件過濾后&#xff0c;剩余數據占總行數的百分比估計值rows * filtered/100 越接近100%效率越高rowspossible_keys 可能選擇的索引key最終決定選擇的行partitions問了哪些分區select_type查詢…

力扣刷題記錄【1】146.LRU緩存

前言&#xff1a; 請你設計并實現一個滿足 LRU (最近最少使用) 緩存 約束的數據結構。 實現 LRUCache 類&#xff1a; LRUCache(int capacity) 以 正整數 作為容量 capacity 初始化 LRU 緩存int get(int key) 如果關鍵字 key 存在于緩存中&#xff0c;則返回關鍵字的值&…

西門子S7-1200 PLC主流通信方法及應用

一、通信基礎 1. 網絡術語與設備 - 關鍵設備&#xff1a;交換機、路由器、網關等。 - 物理接口&#xff1a;RS-485&#xff08;支持多點通信&#xff09;、RS-232C&#xff08;點對點串行通信&#xff09;。 2. OSI參考模型 - 核心框架&#xff1a;理解協議分層&…

MySQL實現任意級子目錄的主要方案以及區別

常見的實現方案及區別 1. 鄰接表&#xff08;Adjacency List&#xff09; 方案描述&#xff1a; 每條記錄存儲一個節點的父節點ID。 表結構大致&#xff1a; id INT PRIMARY KEY, name VARCHAR(...), parent_id INT -- 指向父節點的ID&#xff0c;根節點為NULL或0優點&…

Linux網絡socket套接字(完)(5)

文章目錄前言一、多進程版的Tcp網絡程序捕捉SIGCHLD信號讓孫子進程提供服務二、多線程版的Tcp網絡程序三、線程池版的Tcp網絡程序四、Tcp協議通訊流程通訊流程總覽三次握手的過程數據傳輸的過程四次揮手的過程總結前言 結束嘍&#xff0c;至少這個Tcp套接字有關內容要結束了~ ?…

Web3 Study Log 003

Web3 Study Log 003 2025-7-5 這幾天各種各樣的瑣事&#xff0c;處理完了&#xff0c;真的煩&#xff0c;估計能消停一段時間了… 今天終于能夠坐下來好好學習&#xff0c;今天學習了chainlink的使用&#xff0c;能夠獲取 ETH/USD 實時價格&#xff0c;然后寫了一個簡單的眾…

Kotlin:2.1.20 的新特性

一、概述 The Kotlin 2.1.20 release is here! Here are the main highlights: Kotlin 2.1.20發布了&#xff0c;主要亮點如下&#xff1a; K2 compiler updates: updates to the new kapt and Lombok pluginsKotlin Multiplatform: new DSL to replace Gradle’s Application …

設計模式 | 觀察者模式

觀察者模式&#xff08;Observer Pattern&#xff09;是行為型設計模式中的事件通知專家&#xff0c;它定義了對象間一種一對多的依賴關系&#xff0c;當一個對象狀態改變時&#xff0c;所有依賴它的對象都會自動收到通知并更新。這種模式實現了發布-訂閱機制&#xff0c;是事件…

Apache Struts2 遠程命令執行漏洞(S2-052)

一、漏洞概述 S2-052 是 Apache Struts2 框架中一個高危的遠程代碼執行漏洞&#xff08;CVE-2017-9805&#xff09;&#xff0c;由安全研究人員于 2017 年發現并公開。該漏洞源于 Struts2 的 REST 插件在使用 XStream 組件處理 XML 反序列化時&#xff0c;未對用戶輸入的 XML 數…

RS觸發器Multisim電路仿真——硬件工程師筆記

目錄 1 RS觸發器基礎知識 1.1 工作原理 1.2 電路結構 1.3 特點 1.4 應用 1.5 設計考慮 1.6 總結 2 與非門實現基本RS觸發器 2.1 電路結構 2.2 工作原理 2.3 特點 2.4 總結 3 或非門實現基本RS觸發器 3.1 電路結構 3.2 工作原理 3.3 特點 3.4 總結 4 與非門實…

提示技術系列(12)——程序輔助語言模型

什么是提示技術&#xff1f; 提示技術是實現提示工程目標的具體技術手段&#xff0c;是提示工程中的“工具庫”。 什么又是提示工程&#xff1f; 提示工程是指通過設計、優化和迭代輸入到大語言模型&#xff08;LLM&#xff09;的提示&#xff08;Prompt&#xff09;&#xff…

明遠智睿H618:開啟多場景智慧生活新時代

在數字化浪潮的推動下&#xff0c;智能設備正深刻地改變著我們的生活方式。明遠智睿H618以其強大的功能和卓越的性能&#xff0c;在家庭娛樂、商業展示、教育培訓和智能家居控制等多個領域展現出巨大的應用潛力&#xff0c;開啟了多場景智慧生活的新時代。 家庭娛樂&#xff1…

探秘展銷編輯器:相較于傳統展銷的卓越優勢與甄選指南?

在競爭激烈的商業環境中&#xff0c;企業期望通過展銷活動提升品牌知名度、推廣產品和拓展市場&#xff0c;但傳統展銷方式存在諸多難題。一是場地限制&#xff0c;優質場地稀缺、租金貴、檔期緊&#xff0c;場地空間和布局也不一定合適;二是展示形式單一&#xff0c;多為靜態展…

第31篇:塊設備與字符設備管理深度解析(基于OpenEuler 24.03)

塊設備與字符設備管理深度解析&#xff08;基于OpenEuler 24.03&#xff09; 文章目錄 塊設備與字符設備管理深度解析&#xff08;基于OpenEuler 24.03&#xff09;一、設備基礎概念體系1.1 塊設備的核心特性與分類1.2 字符設備的流式數據模型1.3 設備標識系統&#xff1a;主設…

Django Channels WebSocket實時通信實戰:從聊天功能到消息推送

引言 在Web開發中&#xff0c;實時通信功能&#xff08;如在線聊天、實時通知、數據推送&#xff09;已成為許多應用的核心需求。傳統的HTTP協議由于其請求-響應模式的限制&#xff0c;無法高效實現實時通信。WebSocket作為一種全雙工通信協議&#xff0c;為實時Web應用提供了…

day52 神經網絡調參指南

目錄 隨機種子 內參的初始化 神經網絡調參指南 參數的分類 調參順序 初始化參數 batchsize的選擇 學習率調整 激活函數的選擇 損失函數的選擇 模型架構中的參數 正則化系數 其他補充 隨機種子 import torch import torch.nn as nn# 定義簡單的線性模型&#xf…

.NET9 實現斐波那契數列(FibonacciSequence)性能測試

在 .NET 平臺上實現 斐波那契數列 并使用 BenchmarkDotNet 進行性能測試&#xff0c;是評估不同算法實現方式性能表現的一種高效且標準化的方法。通過該方式&#xff0c;可以對比遞歸、迭代、記憶化遞歸以及結合高性能優化技術&#xff08;如 Span<T>、Memory<T> 和…

三、docker軟件安裝:gitlab,nexus,mysql8,redis,nacos,nginx

目錄 1.gitlab安裝 2.nexus安裝 (1)下載啟動 (2)設置中央倉庫遠程地址 (3)配置maven的settings.xml 3.mysql8安裝 4.redis安裝 5.nacos安裝 6.nginx安裝 1.gitlab安裝 #創建目錄 cd /usr/local/ mkdir docker cd docker/ mkdir gitlab_docker cd gitlab_docker…

【與AI+】SAP WEBGUI集成開發與SAP INTERNET服務的關系

前言&#xff1a;這是我的水水專欄第五篇文章&#xff0c;這個專欄呢&#xff0c;是放一些我向AI提問的問題&#xff0c;以及AI的回答。因為感覺真的好方便哈哈哈~ 我不是很確定我的專欄文章內容是否涉及版權&#xff0c;以及也不確定這些整合過的文字是否涉嫌抄襲&#xff0c…

淺談幾種js設計模式

JavaScript設計模式是開發中常用的一種解決方案&#xff0c;它們幫助開發者以一種更結構化、更易維護的方式編寫代碼。本文將深入介紹幾種常見的JavaScript設計模式&#xff0c;包括單例模式、工廠模式、觀察者模式和策略模式。 一、單例模式&#xff08;Singleton Pattern&am…