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次,完成了參考計數功能

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/75487.html
標籤:C++
上一篇:C++關鍵字總結【新手必學】
下一篇:C++位運算
