智能指針(一):STL auto_ptr實現原理
智能指針實際上是一個類(class),里面封裝了一個指針.它的用處是啥呢?
指針與內存
說到指針自然涉及到內存.我們如果是在堆棧(stack)中分配了內存,用完后由系統去負責釋放.如果是自定義類型,就會自動的去調用你的析構函數.
但如果是在堆(heap)中分配了內存,也就是用malloc或者new.那只能自動手動的使用free或delete去釋放.所以使用heap時處理的不好很容易出現啥內存泄露(內存沒有釋放掉).或者如果你delete一次了,但沒讓它賦值為0,然后再delete一次就導致未定義行為了.
于是你想如果系統能也像管理stack一樣來管理你的heap區域.不用再擔心內存的分配與釋放該多好啊.事實上Java,C#都這樣去做了.也給你去管理heap區域了(所有的自定義類型實例化時都去heap區域獲取內存了.也沒于提供指針的功能.你想自己去釋放內存都不給你這機會了.JVM或者CLR會在后臺自動的去給你釋放掉那些不用的內存.當然這樣一來效率自然沒有你手動釋放來的高了.)
假如有一個指針指向一塊分配的內存,智能指針就是把該指針封裝起來,然后用完了會自動去釋放內存(通過智能指針類的析構函數).這樣你就不用擔心沒有去釋放內存了.當然并不是說你使用了智能指針就能像使用Java,C#一樣不用再擔心內存問題了.智能指針在使用的時候還會存在很多的問題.
據說JVM,CRL也是用(c與c++)實現的.不知道那里面也有用到智能指針不.
智能指針的實現
如果你自己要封裝一個指針你會咋整呢?來來個初稿瞧瞧
1.最精簡版本
template< class T>
class my_auto_ptr {
public:
T* m_ptr; //被封裝的指針
public:
my_auto_ptr( T* p) :m_ptr( p ) { } //構造函數
~my_auto_ptr() { delete m_ptr; } //析構函數
}
上面的自然是最精簡版的,只一個成員變量,構造函數和析造函數.不過雖然簡單其實也能拿來用了啊.比如:
my_auto_ptr<int> myPtr( new int(88) ); //等價int* ip = new int(88); 但這樣你得手動delete ip;而用了智能指針就不用手動delete了.
cout<< *myPtr.m_ptr; //相當于cout<<*ip;
2.改進版本(重載運算符使類用起來像指針)
上面的的精簡版本用起來還挺麻煩.我們是希望封裝了指針類用起來跟指針本身一樣才好.所以需要重載-> , * 等運算符
template< class T>
class my_auto_ptr {
private:
T* m_ptr; //被封裝的指針
public:
my_auto_ptr( T* p) :m_ptr( p ) { }
~my_auto_ptr() { delete m_ptr; }
T& operator*() { return *m_ptr;}
T* operator->() { return m_ptr;}
}
現在my_auto_ptr可以變得很像指針了
my_auto_ptr<int> mp(new int(88) ); //等價int* ip = new int(88);
int num = *mp; //等價int num = *ip;
假如有這樣的類struct Arwen { void Test() { cout<"i am arwen"<<; }
則my_auto_ptr<Arwen> mp( new Arwen); //等價Arwen* ip = new Arwen;
mp->Test(); //等價ip-Test();
3.完善版本(復制構造)
一個完善點的類往往還涉及到復制構造的一些操作.也可以做把另外一個智能指針類做為構造函數的參數,或者通過=給一個類賦值
template< class T>
class my_auto_ptr {
private:
T* m_ptr;
T* GetPtr(){ //供構造賦值時使用
T* tmp = m_ptr;
m_ptr = 0;
return tmp;
}
public:
explicit my_auto_ptr( T* p = 0) :m_ptr( p ) { }
~my_auto_ptr() { delete m_ptr; }
T& operator*() { return *m_ptr;}
T* operator->() { return m_ptr;}
my_auto_ptr(my_auto_ptr& mp){ //復制構造函數
m_ptr = mp.GetPtr(); //mp復制過來后它自己原來的指針相當于失效了.
}
my_auto_ptr& operator=(my_auto_ptr& ap){ 造型賦值操作符
if(ap != *this)
{
delete m_ptr;
m_ptr = ap.GetPtr();
}
return *this;
}
void reset(T* p){ //指針重置,相當于把指針指向另外一個地方去
if(p != m_ptr)
delete m_ptr;
m_ptr = p;
}
};
使用舉例:
假如有類struct Arwen{
int age;
Arwen(int gg) :age(gg) { };
};
void main()
{
my_auto_ptr<Arwen> myPtr( new Arwen(24) );
int num =myPtr->age; //正確
my_auto_ptr<Arwen> ptrOne( myPtr); //復制構造
//num =myPtr->age; 該處會出錯.因為把myPtr復制給ptrOne后,它自己本身相當于失效了
num = ptrOne->age; //正確
my_auto_ptr<Arwen> ptrTwo = ptrOne;
//num = ptrOne->age;該處也會出錯,此時ptrOne也失效了
num = ptrTwo->age; //正確
Arwen* pArwen = new Arwen( 88 );
ptrTwo.reset( pArwen);
num = pArwen->age; //此處的值是88了,而不是以前的24
return 0;
}
auto_ptr的缺陷
上面我實現的my_auto_ptr基本上是實現了auto_ptr的所有核心功能.從里面我們可以明顯的看到一個很大缺陷.我們看到當通過復構造函數,通過操作符=賦值后,原來的那個智能指針對象就失效了.只有新的智能指針對象可以有效使用了.用個專業點的說法叫所有權的轉移.被包裝的指針指向的內存塊就像是一份獨享占用的財產,當通過復制構造,通過=賦值傳給別的智能指針對象時,原有的對象就失去了對那塊內存區域的擁有權.也就是說任何時候只能有一個智能指針對象指向那塊內存區域,不能有兩個對象同時指向那塊內存區域.
這樣一來auto_ptr不能做為STL中容器的元素,為啥呢? 因為容器中經常存在值拷貝的情況嘛,把一個容器對象直接賦值給另一對象.完了之后兩個容器對象可得都能用啊.而如果是auto_ptr的話顯然賦值后只能一個有用,另一個完全報廢了.另外比如你有個變量auto_ptr<int> ap( new int(44) ); 然后ap被放進一個容器后,ap就報廢不能用了.
不過沒辦法啊,在c++ 11標準之前,現在我們大部分用的是98標準吧,STL里面只有auto_ptr這一種智能指針.而在11標準中除了auto_ptr還有如下三種:
unique_ptr
smart pointer with unique object ownership semantics
只能有一個主人的指針,可以用于STL容器
shared_ptr
smart pointer with shared object ownership semantics
可共享的指針
weak_ptr
weak reference to an object managed by std::shared_ptr
弱引用指針
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成