C++(淺析)智能指針及C#GC(垃圾回收機制)分析[圖]

C++(淺析)智能指針及C#GC(垃圾回收機制)分析[圖]
c++中咱們經常使用運算符new和delete來分配和釋放動態內存,然而動態內存的管理很是容易出錯 
使用new 和delete 管理內存存在三個常見問題: 
1.忘記delete(釋放) 內存。(或者異常致使程序過早退出,沒有執行 delete)忘記釋放動態內存會致使人們常說的 內存泄露 問題,你申請了內存而爲歸還給操做系統長時間這樣會致使系統內存愈來愈小。 
(內存泄露問題每每很難查找到,內存耗盡時,才能檢測出這種錯誤) 
2.使用已經釋放掉的對象。好比:咱們使用delete釋放掉申請的內存空間,但並未幹掉指向這片空間的指針,此時指針指向的就是「垃圾」內存。 
3.同一塊內存釋放兩次。當有兩個指針指向相同的動態內存分配對象時,其中一個進行了delete操做 對象內存就還給了操做系統 ,若是咱們要delete第二個指針,那麼內存有可能遭到破壞(淺拷貝問題)
爲了更容易同時也更安全的使用動態內存標準庫爲咱們提供了 智能指針 來管理動態對象,智能指針的行爲相似常規的指針,可是它並不是指針,它是一個存儲指向動態分配(堆)對象指針的類。 html

C++(淺析)智能指針及C#GC(垃圾回收機制)分析[圖]
這個類的構造函數中傳入一個普通指針,析構函數中釋放傳入的指針(釋放管理的堆空間)。智能指針的類都是棧上的對象,因此當函數(或程序)結束時會自動被釋放 
由於智能指針行爲相似於指針 因此通常的指針行爲智能指針也具有(每一個智能指針重載了->和 *)
T& opertaor*(){
return *_ptr;   
} //這裏返回了一個對象T 咱們須要對T進行改動 因此返回T&
T* operator->(){
return _ptr; 
} //這裏至關因而返回了一個指針,而後在使用這個指針指向一個內容
話很少說首先讓咱們來看第一種智能指針auto_ptr(管理權限轉讓) 
咱們常常會遇到這種問題好比說淺拷貝的時候多個指針管理空間 可是當其中一個指針先結束釋放了這塊空間 ,那麼當其餘指針結束時在對這片空間進行釋放 程序就會崩潰。 
爲了方便解決上述問題我就可使用智能指針auto_ptr 其主要原理是是在構造對象時賦予其管理空間的全部權,在拷貝或賦值中轉移空間的全部權 拷貝和賦值後直接將_ptr賦爲空,禁止其再次訪問原來的內存空間。 
//auto_ptr的簡單實現
template<class T>
class Autoptr
{
public:
Autoptr(T* ptr=NULL)
:_ptr(ptr)
{}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
Autoptr(Autoptr<T>& ap){ //拷貝構造
this->_ptr = ap._ptr;
ap._ptr = NULL;
}
Autoptr<T>& operator=(Autoptr<T>& ap){
if (this != &ap){
delete this->_ptr;
this->_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~Autoptr(){
if (_ptr){
cout << "釋放空間le" << endl;
delete _ptr;
}
}
private:
T* _ptr;
};
int main(){
//Autoptr<int>ap1(new int);
//*ap1 = 10;
//Autoptr<int>ap2(ap1);
//Autoptr<int>ap3(ap2);
//*ap3 = 20;
//ap2 = ap3;
//cout << *ap2 << endl;
Autoptr<int>ap1(new int);
*ap1 = 10;
Autoptr<int>ap2(ap1);
cout << *ap1 << endl;//調試到這一步程序崩潰了,罪魁禍首就是   AutoPtr<int>ap2(ap1),
//這裏緣由就是ap2徹底的奪取了ap1的管理權。
//致使ap1被置爲NULL,訪問它的時候程序就會崩潰。
system("pause");
return 0;
}
因爲它實現了徹底的權限轉移,因此致使在拷貝構造和賦值以後只有一個指針可使用,而其餘指針都置爲NULL,使用很不方便,並且還很容易對NULL指針進行解引用,致使程序崩潰,其危害也是比較大的。 
爲了解決 auto_ptr 帶來的問題另外一種智能指針橫空出世—–scoped_ptr(防拷貝) 它的實現原理是直接將拷貝構造和賦值運算符設置爲私有或保護只聲明不定義 
防止他人在類外定義,這樣一次就只有一個指針對空間進行管理就不會出現上面的問題。
scopd_ptr ( unique_ptr )
//Scoped_ptr 獨佔資源
template<class T>
class Scoped_ptr
{
public:
Scoped_ptr(T* _Ptr=NULL)
:_ptr(ptr)
{}
T& operator*(){
return *_ptr;
}
T* operator->(){
return _ptr;
}
~Scoped_ptr()
{
if (_ptr){
delete _ptr;
}
}
private:
Scoped_ptr(const Scoped_ptr<T>&);
//{}
Scoped_ptr<T>& operator=(Scoped_ptr<T>& ap);
private:
T* _ptr;
};
//int main(){
//Scoped_ptr<int> sp1(new int);
//Scoped_ptr<int> sp2(sp1);  不能夠進行拷貝構造 此函數設置爲私有 不可進行訪問
//system("pause");
//return 0;
//}
第三種智能指針shared_ptr (引用計數版本)容許多個指針指向同一對象 
其原理是 經過引用計數記錄對當前操做的指針的個數當進行拷貝構造或者賦值時_pCount++, 析構時當_pCount爲0時才進行釋放空間。 
//簡單實現
template<class T>
class Shared_ptr{
public:
Shared_ptr(T* ptr = NUll)
:_ptr(ptr)
, _pCount(new int(1))
{}
/*{
if (_ptr){
_pCount = new int(1);
}
}*/
Shared_ptr(const Shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_pCount(sp._pCount)
{
++GetRef();
}
//當進行拷貝或賦值操做時 每一個shared_ptr都會有一個計數器 記錄着和它指向相同空間的shared_ptr的個數
Shared_ptr<T>& operator=(const Shared_ptr<T>& sp){
if (this != &sp)
{
Release();
_ptr = sp._ptr;
_pCount = sp._pCount;
++GetRef();
}
return *this;
}
T& operator*(){
return *_ptr;
}
T* operator->(){
return _ptr;
}
~Shared_ptr(){
Release();
}//當指向一個對象的最後一個shared_ptr被銷燬時,shared_ptr類纔會自動銷燬此對象
int Usecount(){
return GetRef();
}
private:
void Release(){
if (0 == --*_pCount &&_ptr){
delete _ptr;
delete _pCount;
_ptr = NULL;
_pCount = NULL;
}
}
int& GetRef(){
return *_pCount;
}
private:
T* _ptr;
T* _pCount;
};
/*int main(){
Shared_ptr<int> sp1(new int(10));
Shared_ptr<int> sp2(sp1);
*sp1 = 10;
*sp2 = 20;
cout << sp1.Usecount() << endl;
cout << sp2.Usecount() << endl;
Shared_ptr<int> sp3(new int(30));
Shared_ptr<int> sp4(sp3);
sp4 = sp2;
*sp4 = 40;
cout << sp3.Usecount() << endl;
cout << sp4.Usecount() << endl;
system("pause");
return 0;
}*/
看完shared_ptr基本原理 咱們在來分析分析它的缺陷(循環引用問題) 
例:
#include<memory>
template<class T>
struct ListNode{
//ListNode<T>* _next;
shared_ptr<ListNode<T>> _next;
//ListNode<T>* _prev;
shared_ptr<ListNode<T>> _prev;
T _data;
ListNode(const T& data = T())
:_next(NULL)
, _prev(NULL)
, _data(data)
{
cout << "LisNode(const T& data)" << this << endl;
}
~ListNode(){
cout << "~ListNode():" << this << endl;
}
};
void Testshared_ptr(){
shared_ptr<ListNode<int>> p1(new ListNode<int>(10));
shared_ptr<ListNode<int>> p2(new ListNode<int>(20));
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
p1->_next = p2;
p2->_prev = p1;
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
}
int main(){
Testshared_ptr();
system("pause");
return 0;
}
這裏寫圖片描述
爲了解決shared_ptr中的循環引用問題 咱們引入shared_ptr的助手指針 weak_ptr 咱們來看看代碼怎麼改
#include<memory>
template<class T>
struct ListNode{
//ListNode<T>* _next;
weak_ptr<ListNode<T>> _next;
//shared_ptr<ListNode<T>> _next;
//ListNode<T>* _prev;
weak_ptr<ListNode<T>> _prev;
//shared_ptr<ListNode<T>> _prev;
T _data;
ListNode(const T& data = T())
/*:_next(NULL)
, _prev(NULL)
, _data(data)*/
:_data(data)
{
cout << "LisNode(const T& data):" << this << endl;
}
~ListNode(){
cout << "~ListNode():" << this << endl;
}
};
這裏寫圖片描述
weak_ptr是爲了配合shared_ptr而引入的一種智能指針,由於它不具備普通指針的行爲,沒有重載operator*和->,它的最大做用在於協助shared_ptr工做,像旁觀者那樣觀測資源的使用狀況。弱引用能檢測到所管理的對象是否已經被釋放,從而避免訪問非法內存。同時,weak_ptr 必須從一個share_ptr或者另外一個weak_ptr轉換而來,不能使用new 對象進行構造。因爲弱引用不更改引用計數,相似普通指針,只要把循環引用的一方使用弱引用,便可解除循環引用。
C# GC(垃圾回收機制):
1,什麼是資源:
所謂的資源就是程序中可利用的數據,譬如:字符串、圖片和任何二進制數據,包括任何類型的文件。
2,訪問資源的步驟:
1)分配內存:分配必定的內存空間。
2)  初始化內存: 一個類型的實例構造器負責這樣的初始化工做。
3)使用資源: 經過訪問類型成員來使用資源。根據須要會有反覆。
4)銷燬資源: 執行清理工做。
什麼是託管資源,非託管資源:託管資源是由CLR全權負責的資源,CLR不負責的資源位非託管資源。
對於託管資源經過GC自動清理回收。對於非託管資源,經過代碼調用手動進行清除,再由GC回收。
如何正確的釋放資源:對於非託管的資源,通常就是,Stream(流),數據庫的鏈接,網絡鏈接等的這些操做系統資源,須要咱們手動去釋放。
Net提供了三種釋放方法:Dispose,Close,析構函數(也就是Finalize方法)
第一種:提供Close方法:
介紹:關閉對象資源,在顯示調用時被調用。
Close 表示什麼意思,它會不會釋放資源,徹底由類設計者決定。由於Close方法是不存在的。你不寫就沒有。那爲何要加一個Close方法呢?爲了不不熟悉C#語法的開發人員更直觀的釋放資源,所以提供了Close方法。提供一個Close方法僅僅是爲了更符合其餘語言(如C++)的規範。正常狀況Close方法裏面會調用Dispose()方法。
第二種:Dispose
繼承IDisposable接口,實現Dispose方法;
介紹:調用Dispose方法,銷燬對象,須要顯示調用或者經過using語句,在顯示調用或者離開using程序塊時被調用。
Dispose方法用於清理對象封裝的非託管資源,而不是釋放對象的內存,對象的內存依然由垃圾回收器控制。
Dispose方法調用,不但釋放該類的非託管資源,還釋放了引用的類的非託管資源。
Dispose模式就是一種強制資源清理所要遵照的約定;Dispose模式實現IDisposable接口,從而使得該類型提供一個公有的Dispose方法。
疑問1:Dispose內部到底如何去清理資源的?
第三種:析構函數(也就是Finalize方法)
一個正常狀況的類是不會寫析構函數的,而一旦一個類寫了析構函數,就意味着GC會在不肯定的時間調用該類的析構函數,判斷該類的資源是否須要釋放,而後調用finalize方法,若是重寫了finalize方法則調用重寫的finalize方法。
Finalize方法的做用是保證.NET對象能在垃圾回收時清除非託管資源。
在.NET中,Object.Finalize()方法是沒法重載的,編譯器是根據類的析構函數來自動生成Object.Finalize()方法的
finalize由垃圾回收器調用;dispose由對象調用。
finalize無需擔憂由於沒有調用finalize而使非託管資源得不到釋放,由於GC會在不肯定時間調用,固然,你也能夠手動調用finalize方法,而dispose必須手動調用。
finalize雖然無需擔憂由於沒有調用finalize而使非託管資源得不到釋放,但由於由垃圾回收器管理,不能保證當即釋放非託管資源;而dispose一調用便釋放非託管資源。
只有類類型才能重寫finalize,而結構不能;類和結構都能實現IDispose.緣由請看Finalize()特性。
雖然能夠手動釋放非託管資源,咱們仍然要在析構函數中釋放非託管資源,這樣纔是安全的應用程序。不然若是由於程序員的疏忽忘記了手動釋放非託管資源, 那麼就會帶來災難性的後果。激盪三十年讀後感(http://www.simayi.net/duhougan/5181.html)心得體會,因此說在析構函數中釋放非託管資源,是一種補救的措施,至少對於大多數類來講是如此。 
因爲析構函數的調用將致使GC對對象回收的效率下降,因此若是已經完成了析構函數該乾的事情(例如釋放非託管資源),就應當使用SuppressFinalize方法告訴GC不須要再執行某個對象的析構函數。 
析構函數中只能釋放非託管資源而不能對任何託管的對象/資源進行操做。由於你沒法預測析構函數的運行時機,因此,當析構函數被執行的時候,也許你進行操做的託管資源已經被釋放了。這樣將致使嚴重的後果。 
在結構上重寫Finalize是不合法的,由於結構是值類型,不在堆上,Finalize是垃圾回收器調用來清理託管堆的,而結構不在堆上。
帶有析構函數的類,生命週期會變長。內存空間須要兩次垃圾回收纔會被釋放,致使性能降低。
一個帶有析構的類,它引用了不少其餘的類,這將致使這些類都升到第1代。(gc有0,1,2三代回收機制)。
5)釋放內存:託管堆上的內存由GC全權負責, 值引用的在棧上的內存會隨着棧空間的消亡而自動消失。
GC.Collect(); //強制對全部代進行即時垃圾回收
當應用程序代碼中某個肯定的點上使用的內存量大量減小時,在這種狀況下使用 GC.Collect 方法可能比較合適。
疑問2:垃圾回收機制的原理?強制垃圾回收是怎麼一回事?
3,釋放模式:是一種微軟建議的寫法,先手動顯示去釋放資源,若是忘記了,再讓finalize釋放資源。若是dispose調用了,則析構不會再調用(使用SuppressFinalize方法取消析構函數的調用)。
其餘:
若是引用類型對象再也不須要,是否須要顯式=null;答案是:即便不這樣作,GC也會進行垃圾回收。c++

相關文章
相關標籤/搜索