為什麼有智能指針?
在我們寫代碼時,經常會忘記釋放掉動态開辟出來的記憶體,或者在我們的程式中,new和delete中間有如 throw/goto/return/break 這樣引起執行流改變的語句時,就會造成沒有釋放資源,造成記憶體洩漏。
void Test1()
{
int* p=new int[];
Test();
...
delete[] p;
調用的Test2()中如果有抛異常的話,就會無法釋放p,造成記憶體洩漏,
這種情況就需要用智能指針來解決。
智能指針
所謂“智能指針”就是比一般的指針聰明,它可以智能/自動的釋放掉指針所指向的動态資源空間。
那麼這種智能指針是任何實作的呢?
在這裡我們要提到一種思想
RAII(Resource Acquisition Is Initialization)
資源配置設定即初始化,定義一個類來封裝資源的配置設定和釋放,在構造函數完成資源的配置設定和初始化,在析構函數完成資源的清理,可以保證資源的正确初始化和釋放。
是以智能指針其實就是一個類
智能指針(自動釋放,可以像指針一樣用)
1、自動釋放
一個類裡(構造,析構)
//RAII思想
2、像指針一樣
通過運算符的重載實作(* 和->)
T& operator* ()
{
return *_ptr;
}
T* operator-> ()
{
return _ptr;
}
注意函數傳回值類型
上面說了智能指針是什麼,以及為什麼存在這種智能指針,那麼下面我們再來看看智能指針都有那些代表,以及它的發展
早期c++98 | auto_ptr | 管理權轉移,帶有缺陷 | |
---|---|---|---|
boost | scoped_ptr | 守衛指針 | 防拷貝 |
scoped_arrary | |||
shared_ptr | 共享指針 | 引用計數,缺陷循環引用 | |
shared_array | |||
weak_ptr | 弱指針 | 不單獨存在,輔助解決shared_ptr的循環引用問題 |
一、模拟實作AutoPtr指針
// 智能指針--類(AutoPtr指針)
//首先是個類
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)//構造
:_ptr(ptr)
{}
~AutoPtr()//析構,清理資源
{
if(_ptr)
{
printf("0x%p\n", _ptr);
delete _ptr;
}
}
// ap2(ap1)
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = NULL;
}
// ap1 = ap2
AutoPtr<T>& operator=(AutoPtr<T>& ap)
{
if (this != &ap)
{
if(_ptr)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
// 運算符的重載
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
struct AA
{
int _a1;
int _a2;
};
//寫個函數對比測試一下
void Func ()
{
int* p1 = new int();
*p1 = ;
// RAII
AutoPtr<int> ap1(new int());
*ap1 = ; //ap1.operator*() = 20;
AA* p2 = new AA;
p2->_a1 = ;
p2->_a2 = ;
AutoPtr<AA> ap2(new AA);
ap2->_a1 = ;
ap2->_a2 = ;
}
void TestAutoPtr()
{
//Func();
int* p1 = new int();
int* p2 = p1;//沒有釋放記憶體,清理對象
// 深拷貝
AutoPtr<int> ap1(new int());
AutoPtr<int> ap2 = ap1;
AutoPtr<int> ap3(new int());
ap2 = ap3;
}
分析:
與普通指針的異同

對比普通指針和智能指針,智能指針可以實作*/->兩個功能,同時可以自動清理資源
管理權的轉移
我們可以看到這裡就展現出了auto_ptr這種智能指針的特點,也是它的缺陷,當另一個指針指向時,之前的指針就對它沒有任何權利了,變為空指針,權利全部給新的指針,一個auto_ptr,隻能管理一塊空間(就像一個人隻能有一個男朋友/女朋友,分手後,前女友就和你沒有關系了,珍惜現女友)
二、模拟實作Scoped_ptr指針
這個指針的特點是防拷貝
如何實作防拷貝
1.隻聲明不實作
2.私有
template<class T>
class ScopedPtr
{
public:
// RAII
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
~ScopedPtr()
{
if(_ptr)
delete _ptr;
}
// 像指針一樣
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
// 防拷貝
// 1.隻聲明不實作
// 2.私有
private:
ScopedPtr(const ScopedPtr<T>& sp);
ScopedPtr<T>& operator=(const Scopedtr<T>& sp);
//将拷貝構造和operator=封在類裡,設為私有
private:
T* _ptr;
};
void TestScopedPtr()
{
ScopedPtr<int> sp1(new int());
//ScopedPtr<int> sp2(sp1);錯誤使用
ScopedPtr<int> sp3(new int());
//sp1 = sp3;錯誤使用
}
三、Shared_ptr共享指針&Weak_ptr弱指針
(這兩個指針要結合起來使用)
auto_ptr能共享空間,scoped_ptr不能拷貝,都很bug,但我們還有shared_ptr(當然它也有循環引用的問題,是以有弱指針輔助)
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = NULL)
:_ptr(ptr)
,_refCount(new int())
{}
~SharedPtr()
{
if (--(*_refCount) == )
{
cout <<"釋放了 "<< endl;
delete _ptr;
delete _refCount;
}
}
// sp2(sp1)
SharedPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
,_refCount(sp._refCount)
{
(*_refCount)++;
}
// sp2 = sp1
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
if (_ptr != sp._ptr)
{
if (--(*_refCount) == )
{
delete _ptr;
delete _refCount;
}
_ptr = sp._ptr;
_refCount = sp._refCount;
(*_refCount)++;
}
return *this;
}
T& operator*()
{...}
T* operator->()
{...}
int UseCount()
{
return *_refCount;
}
private:
T* _ptr;
int* _refCount;
};
上面的程式隻是shared_ptr的部分功能的思想展現,不是真正的模拟shared_ptr指針,真正的shared_ptr在boost庫裡是很複雜的。
下面我們來說剛才寫道的循環引用問題
struct ListNode
{
int _data;
SharedPtr<ListNode> _next;
SharedPtr<ListNode> _prev;
/*WeakPtr<ListNode> _next;
WeakPtr<ListNode> _prev;*/
~ListNode()
{
cout<<"~ListNode()"<<endl;
}
};
void TestSharedPtrRef()
{
SharedPtr<ListNode> cur(new ListNode);
SharedPtr<ListNode> next(new ListNode);
cout<<cur.UseCount()<<endl;//1
cout<<next.UseCount()<<endl;//1
cur->_next = next;
next->_prev = cur;
cout<<cur.UseCount()<<endl;//2
cout<<next.UseCount()<<endl;//2
}
這裡的循環引用問題出現在了哪呢?
當我們調析構函數釋放空間時,就出問題了
當我們要是放cur時,如果把cur直接釋放,那麼next的_prev就成了野指針。同理next節點也是一樣的。 (其實就是當要釋放cur節點時,先要清理掉它内部的指針,可是cur->next指向next,next->prev又指向cur,是以他們就成了一個死循環,一直就無法釋放)
是以循環引用就是兩個對象互相牽制了對方,進而導緻兩個對象誰都無法被釋放,引發了記憶體洩露
運作程式時就會發現程式死循環了
是以為了解決這種問題,我們就要用weak_ptr我們隻需要将節點的_next和_prev計數就好,不需要把shared_ptr的指針也計數,就可以解決問題
template<class T>
class WeakPtr
{
public:
WeakPtr()
:_ptr(NULL)
{}
WeakPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
{}
WeakPtr<T>& operator=(const SharedPtr<T>& sp)
{
_ptr = sp._ptr;
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};