Java面試官經(jīng)常喜歡問關于垃圾回收的問題液肌。而他最終給出的答案往往是:給對象中添加一個引用計數(shù)器承疲,每當有一個地方引用它時,計算器值就加1冲呢;當引用失效時舍败,計數(shù)器值就減1;任何時候計數(shù)器為0的對象就是不可能再被使用的敬拓。
客觀的說邻薯,引用計數(shù)算法的實現(xiàn)簡單,判定效率也很高乘凸,在大部分情況下它都是一個不錯的算法厕诡。但是,至少主流的Java虛擬機里面沒有選用引用計數(shù)算法來管理內(nèi)存营勤,其中最主要的原因是它很難解決對象之間的相互循環(huán)引用的問題灵嫌。在主流的商用程序語言(Java、C#)的主流實現(xiàn)中通過可達性分析來判定對象存活的葛作。
這里寿羞,我們不去談可達性分析策略。至少赂蠢,引用計數(shù)算法還是有一定的用武之地的绪穆。比如說,蘋果為自家的ios開發(fā)編程語言objective-c引用了ARC機制來進行內(nèi)存管理虱岂,在很大程度上消除了手動內(nèi)存管理的負擔玖院。為了避免對象之間循環(huán)引用,我們可以將對象聲明為弱引用第岖。而在C++中司恳,內(nèi)存的分配和釋放需要手動來管理,這在一定程度上帶來了內(nèi)存泄漏的隱患绍傲。幸運的是,C++標準庫中提供了一種叫做智能指針(shared_ptrs)的類,智能指針的作用有如同指針烫饼,但會記錄有多少個shared_ptrs共同指向一個對象猎塞。這便是所謂的引用計數(shù)。一旦最后一個這樣的指針被銷毀杠纵,也就是一旦某個對象的引用計數(shù)變?yōu)?荠耽,這個對象會被自動刪除。
比如說比藻,用智能指針來創(chuàng)建一個動態(tài)分配的字符串對象:
//新創(chuàng)建一個對象铝量,引用計數(shù)器為1
shared_ptr<string> pstr(new string("abc"));
解引用一個智能指針返回它指向的對象。同樣银亲,我們可以像操作普通指針一樣調(diào)用string提供的方法慢叨。
if (pstr && pstr->empty()) {
*pstr = "hello";
}
當有另外一個智能指針對當前智能指針進行拷貝時,引用計數(shù)器加1:
shared_ptr<string> pstr(new string("abc")); //pstr指向的對象只有一個引用者
shared_ptr<string> pstr2(pstr); //pstr跟pstr2指向相同的對象务蝠,此對象有兩個引用者
當兩個智能指針進行賦值操作時拍谐,左邊的指針指向的對象引用計數(shù)減1,右邊的加1馏段。
shared_ptr<string> pstr(new string("abc"));
shared_ptr<string> pstr2(new string("hello"));
pstr2 = pstr; //給pstr2賦值轩拨,令他指向另一個地址,遞增pstr指向的對象的引用計數(shù)院喜,遞減pstr2原來指向的對象引用計數(shù)
指針離開作用域范圍時凡蜻,同樣引用計數(shù)減1。當引用計數(shù)為0時盖文,對象被回收废士。
根據(jù)以上的分析,我們對它做一個簡單的實現(xiàn):
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(); //自定義析構(gòu)函數(shù)
private:
int *count; //引用計數(shù)
T *p; //智能指針底層保管的指針
};
跟標準庫一樣元咙,我們使用模板來實現(xiàn)它梯影。
用普通指針進行初始化時,需要將該指針進行封裝庶香,并且引用計數(shù)初始化為1甲棍。
template <typename T>
smart_ptrs<T>::smart_ptrs(T *p): count(new int(1)), p(p) {
}
定義拷貝構(gòu)造函數(shù):
template <typename T>
//對普通指針進行拷貝,同時引用計數(shù)器加1赶掖,因為需要對參數(shù)進行修改感猛,所以沒有將參數(shù)聲明為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;
}
定義賦值運算符奢赂,左邊的指針計數(shù)減1陪白,右邊指針計數(shù)加1,當左邊指針計數(shù)為0時膳灶,釋放內(nèi)存:
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;
}
定義析構(gòu)函數(shù):
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;
為了讓測試結(jié)果更明顯序厉,我在方法中加入了一些輸出锐膜,測試結(jié)果如下: