1. 程式人生 > >定義行為像指標的類(類比於智慧指標)和定義行為像值的類

定義行為像指標的類(類比於智慧指標)和定義行為像值的類

一直糾結於智慧指標shared_ptr的引用計數如何實現。此前看C++ primer不太注意,今天再次翻到這一知識點,再細看一遍,受益非淺呀。原來引用計數的實現也不過如此。

1、定義行為像指標的類

這裡為避免同名,且只強調引用計數的實現,自定義了一個HasPtr類,其中最大特點是引入引用計數器。

類指標的類,最大特點是底層資料共享。只對指標進行了淺拷貝。

引用計數的工作方式如下四大特點:

i、除了初始化物件外,每個建構函式(拷貝建構函式除外)還要建立一個引用計數來記錄有多少物件與正在建立的物件共享狀態。當建立一個物件時,只有一個物件共享狀態,因此將計數器初始化為1。(初始化物件時,計數器計為1

ii、拷貝建構函式不分配新的計數器,而是拷貝給定物件的資料成員,包括計數器。拷貝建構函式遞增共享的計數器,指出給定物件的狀態又被一個新使用者所共享。(拷貝時,遞增相應引用計數器

iii、解構函式遞減計數器,指出共享狀態的使用者少了一個。直至計數器變為0,則解構函式釋放狀態。(只有計數器為0時,才能釋放共享記憶體

iv、拷貝賦值運算子遞增右側運算物件的計數器。遞減左側運算物件賦值之前物件所持有的引用計數器。如果左側運算物件的計數器變為0,意味著其共享狀態沒有使用者了,拷貝賦值運算子就必須銷燬狀態。(右增左減相對應計數

為此,引用計數器儲存在動態記憶體中。當建立一個物件時,分配一個新的計數器

。當拷貝或幅值物件時,拷貝指向計數器的指標。

具體實現,如下程式碼所示(只關注了引用計數器實現部分哈):

#include<string>
using namespace std;

class HasPtr {
public:
	HasPtr(const string s = string()) ://建構函式
		ps(new string(s)), i(0), use(new size_t(1)) {}
	HasPtr(const HasPtr& rhs) :ps(rhs.ps), i(rhs.i), use(rhs.use) {
		*use++;//拷貝建構函式時,遞增引用計數器
	}
	HasPtr& operator=(const HasPtr& rhs);
	~HasPtr();
private:
	string* ps;
	int i;
	size_t* use;//引用計數器
};

HasPtr::~HasPtr() {
	if (--*use == 0) {//只有當引用計數被減為0時,才釋放動態記憶體
		delete ps;
		delete use;
	}
}

HasPtr& HasPtr::operator=(const HasPtr& rhs) {
	*rhs.use++;//=右邊的引用計數遞增,,所放位置很重要,也起到防自賦值效果
	if (--*use == 0) {//=左邊的引用計數遞減為0時,釋放動態記憶體
		delete ps;
		delete use;
	}
	ps = rhs.ps;
	i = rhs.i;
	use = rhs.use;
	return *this;
}

2、定義行為像值的類

副本與原物件完全獨立,即實現底層資料的深拷貝。

為此,實現HasPtr類需要實現以下三個特點:

i、定義一個拷貝建構函式,完成string的深拷貝,而不是拷貝指標;

ii、定義一個解構函式來釋放string;

iii、定義一個拷貝賦值運算子來釋放物件當前的string,並從右側運算物件拷貝string。(防止自賦值現象)

#include<string>
using namespace std;

class HasPtr {
pubilc:
	HasPtr(const string &s =string()):
		ps(new string(s)),i(0){}
	HasPtr(const HasPtr& rhs):ps(new string(*rhs.ps)),i(rhs.i){}
	HasPtr& operator=(const HasPtr& rhs);
	~HasPtr() {
		delete ps;
	}

private:
	string *ps;
	int i;
};

HasPtr& HasPtr::operator=(const HasPtr& rhs) {
	HasPtr* tmp = new string(*rhs.ps);//先深拷貝,
	delete ps;//釋放本物件的動態記憶體
	ps = tmp;//賦值
	i = rhs.i;
	return *this;//返回本物件
}