文章目錄
- 1.最精簡版本
- 2.改進版本(重載運算子使類用起來像指標)
- 3.完善版本(複製構造)
智能指標實際上是一個類(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
弱引用指標