C++引用計數設計與分析(解決垃圾回收問題)

来源:https://www.cnblogs.com/zhaoyixiang/archive/2019/12/29/12116470.html
-Advertisement-
Play Games

1.引言 上一篇博文關於淺拷貝和深拷貝 https://www.cnblogs.com/zhaoyixiang/p/12116203.html 我們瞭解到我們在淺拷貝時對帶指針的對象進行拷貝會出現記憶體泄漏,那C++是否可以實現像python,JAVA一樣引入垃圾回收機制,來靈活的來管理記憶體。 遺憾的 ...


1.引言

上一篇博文關於淺拷貝和深拷貝   https://www.cnblogs.com/zhaoyixiang/p/12116203.html
我們瞭解到我們在淺拷貝時對帶指針的對象進行拷貝會出現記憶體泄漏,那C++是否可以實現像python,JAVA一樣引入
垃圾回收機制,來靈活的來管理記憶體。

遺憾的是C++並不像python、java等編程語言一樣有著垃圾回收機制(Gabage Collector),因此導致了C++中對動態
存儲的管理稱為程式員的噩夢,出現了記憶體遺失(memory leak)、懸空指針、非法指針存取等問題。

Bjarne本人認為:
“我有意這樣設計C++,使它不依賴於自動垃圾回收(通常就直接說垃圾回收)。這是基於自己對垃圾回收系統的經驗,
我很害怕那種嚴重的空間和時間開銷,也害怕由於實現和移植垃圾回收系統而帶來的複雜性。還有,垃圾回收將使C+
+不適合做許多底層的工作,而這卻正是它的一個設計目標。但我喜歡垃圾回收的思想,它是一種機制,能夠簡化設計、
排除掉許多產生錯誤的根源。

C++中提供的構造函數和析構函數就是為瞭解決了自動釋放資源的需求。Bjarne有一句名言,“資源需求就是初始化(Resource Inquirment Is Initialization)”。
因此,我們可以將需要分配的資源在構造函數中申請完成,而在析構函數中釋放已經分配的資源。
在C++中,允許你控制對象的創建,清楚和複製,我們就可以通過開發一種稱為引用計數的垃圾回收機制實現這種控制

2.設計思想

首先我們明確在對存在指針的對象構造時,析構對象時需要把指針delete(釋放掉),但是此時如果我們對對象進行淺拷貝,沒有新的指針new。
析構對象時候會出現記憶體泄漏(一個指針所指的記憶體被兩次釋放的清況),我們用通過引用計數來解決這個問題:

每構造一個對象,就創建一個新的計數器並+1.每拷貝構造一次就在被拷貝的那個對象所在的計數器上+1;
析構時候 按照構造函數析構的順序(後造先放,類似棧),最後構造或拷貝的先釋放;
每次釋放先對計數器-1並判斷計數器是否為0(是否存在淺拷貝的對象),大於0時,繼續按照析構順序析構下一個對象;
當計數器為0時,釋放指針。

3.舉例

我們按順序構造3個對象,計數器標號記為 1,2,3,對第一個和第三個對象淺拷貝兩次,
對對象拷貝完成後計數器1,2,3的值分別為 2 1 2.
先釋放計數器3  計數器-1後等於1,析構掉一個對象。計數器為 2 1 1
再釋放計數器1  計數器-1後等於1,析構掉一個對象。計數器為 1 1 1
再釋放計數器3  計數器-1後等於0,析構掉一個對象,並釋放掉指針。計數器為 1    1   空
再釋放計數器2  計數器-1後等於0,析構掉一個對象,並釋放掉指針。計數器為 1   空   空
再釋放計數器1  計數器-1後等於0,析構掉一個對象,並釋放掉指針。計數器為 空  空   空
最終所有對象析構完畢,指針也全部釋放完

 

4.代碼

//引用計數類

class CRefCount
{
public: 
	CRefCount();     //構造計數器對象
	CRefCount(const CRefCount& obj); //拷貝構造計數器
	void* Alloc(int size); //構造對象時申請空間
	int AddRef();   //計數增加
	int ReleaseRef();	//計數減少
	~CRefCount();	

private:
	void* m_pBuf;   //指針緩衝區
	int* m_pRefCount;  //計數
};


CRefCount::CRefCount()
{
	m_pBuf = nullptr;
	m_pRefCount = nullptr;
}

CRefCount::CRefCount(const CRefCount& obj)
{
	m_pBuf = obj.m_pBuf;
	m_pRefCount = obj.m_pRefCount;
	AddRef();
}

void* CRefCount::Alloc(int size)
{
	m_pBuf = new char[size + 1];  //申請緩衝區
	m_pRefCount = new int(0);
	AddRef();      //每次構造對象計數+1

	return m_pBuf;
}

int CRefCount::AddRef()
{
	if (m_pRefCount == nullptr)
		return 0;
	return ++(*m_pRefCount);
}

int CRefCount::ReleaseRef()
{
	if (m_pRefCount == nullptr)
		return 0;

	return --(*m_pRefCount);
}

CRefCount::~CRefCount()
{
	if (ReleaseRef() == 0)
	{
		if (m_pBuf != nullptr)
		{
			delete[] m_pBuf;
			m_pBuf = nullptr;
			delete m_pRefCount;
			m_pRefCount = nullptr;
		}
	}
}

 5.測試

//student測試用例
#include"CRefCount.h"
#include<iostream>
#pragma warning(disable:4996)

using namespace std;

class CStudent
{
private:
	char* m_pName;
	CRefCount m_RefCount;
	const char* GetName() const;
public:
	CStudent(const char* pName);
};


const char* CStudent::GetName() const
{
	return m_pName;
}

CStudent::CStudent(const char* pName)
{
	m_pName = (char*)m_RefCount.Alloc(strlen(pName) + 1);  //申請一個用來存放名字的空間
	strcpy(m_pName, pName);
}


int main()
{
	CStudent s1("shadow");
	CStudent s2("iceice");
	CStudent s3("maybe");
	CStudent s4 = s1;
	CStudent s5 = s3;

	return 0;
}

 調試這個程式,我們在完成構造和拷貝後,查看記憶體,可以看到此時計數器1,2,3分別對應的值為2,1,2

 

 

單步跟入,看到第一個拷貝構造的對象被析構掉,計數器值-1 ,此時3個計數器值分別為為2,1,1

 

 

 再繼續往後走,發現第二個拷貝對象析構掉切指針所指的記憶體還未被釋放掉,計數器1 -1,此時計數器值為 1,1,1

 

再向後執行,此時第三個構造的對象開始被析構掉同時計數器減到0,此時對象3的指針被釋放掉。

 

 

 加上輔助調試代碼,最終可以看到執行結果,構造3次,拷貝2次,釋放3次,完成了引用計數功能

 


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

-Advertisement-
Play Games
更多相關文章
  • 設計模式就是在大量的實踐中總結和理論之後優選的代碼結構、編程風格、以及解決問題的思考方式。 說白了設計模式就是在實際編程中逐漸總結出的解決問題的套路,類似於數學公式。 類的單例設計模式:在開發過程中有且只有一個實例化對象。 怎麼做到在整個系統運行過程中,這個類只被實例化一次?不論在哪只調用這一個實例 ...
  • 前言 曾經我認為最快的是麥迪的第一步,後來我覺得 7 醬逃跑速度更快,現在我懂了,原來我們都在跟時間賽跑. 年底了,給自己一個 "交代" 吧 2019 的我 2019 年寫了 很多 篇文章, 受到一些贊賞,也遭到一些批評. 我就不要臉地把它們都當做鼓勵吧~ 帶著它們,砥礪前行 !!! 也希望自己 2 ...
  • 新建資料庫my_db,新建表student_tb id為主鍵,不自動遞增 下載MyBatis https://github.com/mybatis/mybatis-3/releases 解壓。 新建Java項目,什麼都不用勾選(如果要在IDEA中操作資料庫,可勾選SQL Support)。 勾選後, ...
  • 轉載!!! 百度網盤鏈接 救救孩子吧!!! 素材+文件+源碼 共16.5G │ 觀看視頻必讀.txt │ 運行必讀.txt │ 運行環境.txt │ ├─資料庫文件 │ db_base_project.sql │ ├─視頻教程 │ 第一講系統開發環境搭建及項目基本情況介紹.mp4 │ 第二講ssm框 ...
  • 這篇文章介紹的內容是關於最全最詳細的PHP面試題(帶有答案),有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下 相關推薦: 分享一波騰訊PHP面試題 2019年PHP最新面試題(含答案) Redis 高級面試題 學會這些還怕進不了大廠? 阿裡面試官三年經驗PHP程式員知識點彙總,學會你就 ...
  • Python第五十一天 python2升級為python3 公司使用的生產環境系統是centos7,所以這裡以centos7系統為基礎,講解將python2升級為python3的方法 centos7系統預設已經安裝了python2.7,但是python2的生命周期到2020年1月1日就會終結,在這個 ...
  • 1.用於檢索敏感辭彙 # !/usr/bin/python # -*- coding:utf-8 -*- info = input('請輸入檢索內容:') li = ['蒼老師','東京熱'] for i in li: if i in info: info = info.replace(i,'*** ...
  • 《瘋狂Java講義(第4版)》是《瘋狂Java講義》的第4版,第4版保持了前3版系統、全面、講解淺顯、細緻的特性,全面新增介紹了Java 9的新特性。 《瘋狂Java講義(第4版)》深入介紹了Java編程的相關方面,《瘋狂Java講義(第4版)》內容覆蓋了Java的基本語法結構、Java的面向對象特 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...