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

1.引言

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

遺憾的是C++並不像python、java等編程語言同樣有着垃圾回收機制(Gabage Collector),所以致使了C++中對動態
存儲的管理稱爲程序員的噩夢,出現了內存遺失(memory leak)、懸空指針、非法指針存取等問題。

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

C++中提供的構造函數和析構函數就是爲了解決了自動釋放資源的需求。Bjarne有一句名言,「資源需求就是初始化(Resource Inquirment Is Initialization)」。
所以,咱們能夠將須要分配的資源在構造函數中申請完成,而在析構函數中釋放已經分配的資源。
在C++中,容許你控制對象的建立,清楚和複製,咱們就能夠經過開發一種稱爲引用計數的垃圾回收機制實現這種控制java

2.設計思想

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

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

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,析構掉一個對象,並釋放掉指針。計數器爲 空  空   空
最終全部對象析構完畢,指針也所有釋放完ios

 

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次,完成了引用計數功能測試

 

相關文章
相關標籤/搜索