Java面試官經常喜歡問關于垃圾回收的問題。而他最終給出的答案往往是:給對象中添加一個引用計數器,每當有一個地方引用它時,計算器值就加1;當引用失效時,計數器值就減1;任何時候計數器為0的對象就是不可能再被使用的。
客觀的說,引用計數算法的實現簡單,判定效率也很高,在大部分情況下它都是一個不錯的算法。但是,至少主流的Java虛擬機里面沒有選用引用計數算法來管理內存,其中最主要的原因是它很難解決對象之間的相互循環引用的問題。在主流的商用程序語言(Java、C#)的主流實現中通過可達性分析來判定對象存活的。
這里,我們不去談可達性分析策略。至少,引用計數算法還是有一定的用武之地的。比如說,蘋果為自家的ios開發編程語言objective-c引用了ARC機制來進行內存管理,在很大程度上消除了手動內存管理的負擔。為了避免對象之間循環引用,我們可以將對象聲明為弱引用。而在C++中,內存的分配和釋放需要手動來管理,這在一定程度上帶來了內存泄漏的隱患。幸運的是,C++標準庫中提供了一種叫做智能指針(shared_ptrs)的類,智能指針的作用有如同指針,但會記錄有多少個shared_ptrs共同指向一個對象。這便是所謂的引用計數。一旦最后一個這樣的指針被銷毀,也就是一旦某個對象的引用計數變為0,這個對象會被自動刪除。
比如說,用智能指針來創建一個動態分配的字符串對象:
//新創建一個對象,引用計數器為1
shared_ptr<string> pstr(new string("abc"));
解引用一個智能指針返回它指向的對象。同樣,我們可以像操作普通指針一樣調用string提供的方法。
if (pstr && pstr->empty()) {
*pstr = "hello";
}
當有另外一個智能指針對當前智能指針進行拷貝時,引用計數器加1:
shared_ptr<string> pstr(new string("abc")); //pstr指向的對象只有一個引用者
shared_ptr<string> pstr2(pstr); //pstr跟pstr2指向相同的對象,此對象有兩個引用者
當兩個智能指針進行賦值操作時,左邊的指針指向的對象引用計數減1,右邊的加1。
shared_ptr<string> pstr(new string("abc"));
shared_ptr<string> pstr2(new string("hello"));
pstr2 = pstr; //給pstr2賦值,令他指向另一個地址,遞增pstr指向的對象的引用計數,遞減pstr2原來指向的對象引用計數
指針離開作用域范圍時,同樣引用計數減1。當引用計數為0時,對象被回收。
根據以上的分析,我們對它做一個簡單的實現:
template <typename T>
class smart_ptrs {
public:
smart_ptrs(T*); //用普通指針初始化智能指針
smart_ptrs(smart_ptrs&);
T* operator->(); //自定義指針運算符
T& operator*(); //自定義解引用運算符
smart_ptrs& operator=(smart_ptrs&); //自定義賦值運算符
~smart_ptrs(); //自定義析構函數
private:
int *count; //引用計數
T *p; //智能指針底層保管的指針
};
跟標準庫一樣,我們使用模板來實現它。
用普通指針進行初始化時,需要將該指針進行封裝,并且引用計數初始化為1。
template <typename T>
smart_ptrs<T>::smart_ptrs(T *p): count(new int(1)), p(p) {
}
定義拷貝構造函數:
template <typename T>
//對普通指針進行拷貝,同時引用計數器加1,因為需要對參數進行修改,所以沒有將參數聲明為const
smart_ptrs<T>::smart_ptrs(smart_ptrs &sp): count(&(++*sp.count)), p(sp.p) {
}
定義指針運算符:
template <typename T>
T* smart_ptrs<T>::operator->() {
return p;
}
定義解引用運算符,直接返回底層指針的引用:
template <typename T>
T& smart_ptrs<T>::operator*() {
return *p;
}
定義賦值運算符,左邊的指針計數減1,右邊指針計數加1,當左邊指針計數為0時,釋放內存:
template <typename T>
smart_ptrs<T>& smart_ptrs<T>::operator=(smart_ptrs& sp) {
++*sp.count;
if (--*count == 0) { //自我賦值同樣能保持正確
delete count;
delete p;
}
this->p = sp.p;
this->count = sp.count;
return *this;
}
定義析構函數:
template <typename T>
smart_ptrs<T>::~smart_ptrs() {
if (--*count == 0) {
delete count;
delete p;
}
}
好了,大功告成!接下來,我們用這段代碼進行測試:
smart_ptrs<string> pstr(new string("abc"));
smart_ptrs<string> pstr2(pstr);
smart_ptrs<string> pstr3(new string("bcd"));
pstr3 = pstr2;
為了讓測試結果更明顯,我在方法中加入了一些輸出,測試結果如下: