1.智能指針的產生背景及發展歷史:
在C++中動態內存的管理由一對運算符來管理:new,爲動態內存分配空間並返回一個指向該對象的指針;delete,接受一個動態對象的指針,並銷燬該對象,釋放與之相關聯的內存。有時我們忘記釋放內存,造成內存泄漏;有時在有指針引用內存的情況下就釋放了它,在這種情況下會產生引用非法內存的指針。
RAII(一種解決問題的思想):
RAII(Resource Acquisition Is Initialization),也稱爲爲“資源獲取就是初始化”,是C++語言的一種管理資源、避免泄漏的慣用法。C++標準保證任何情況下,已構造的對象最終會銷燬,即它的析構函數最終會被調用。簡單的說,RAII 的做法是使用一個對象,在其構造時獲取資源,在對象生命期控制對資源的訪問使之始終保持有效,最後在對象析構的時候釋放資源。通過應用RAII思想,就產生了智能指針:
智能紙指針的行爲類似常規指針;重要的是它負責自動釋放所指的對象。
發展歷程:
2.模擬實現智能指針
1>模擬實現auto_ptr
template <class T>
class AutoPtr
{
public:
AutoPtr(T *ptr)
:_ptr(ptr)
{
}
~AutoPtr()
{
if (_ptr != NULL)
{
delete _ptr;
_ptr = NULL;
}
}
AutoPtr(AutoPtr <T> & ap)
:_ptr(ap._ptr)
{
ap._ptr = NULL;
//只能有一個對象的成員指針指向同一塊區域,前一個對象的成員指針置爲空指針
}
T& operator //注意返回值問題不能返回一個臨時變量,臨時變量具有常性,會導致不能通過解引用更改值
{
return *(this->_ptr);
}
AutoPtr& operator=(AutoPtr &ap)
{
//防止自賦值
if (this != &ap)
{
delete _ptr;
this->_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
T* operator->()
{
//this->_ptr->;
return _ptr;
}
private:
T *_ptr;
};
class AA
{
public:
int _a;
int _b;
};
//測試用例
void TestAutoPtr()
{
AutoPtr<int> ap1(new int);
*ap1 = 10;
AutoPtr<int> ap2(ap1);
//*ap1 = 20;
//這句代碼會導致程序崩潰,因爲當有兩個對象的成員指針指向同一塊區域
//時,前一個對象的成員指針會被置成空指針
AutoPtr<int> ap3(new int(20));
ap2 = ap3;
AutoPtr<AA> ap4(new AA);
ap4->_a = 10;
ap4->_b = 20;
ap2 = ap3;
}
2>模擬實現scoped_ptr(和unique_ptr功能類似)
template<class T>
class ScopedPtr
{
public:
ScopedPtr(T *ptr)
:_ptr(ptr)
{
}
~ScopedPtr()
{
if (_ptr)
{
delete _ptr;
_ptr = NULL;
}
}
T& operator *()
{
return *(_ptr);
}
T* operator ->()
{
return _ptr;
}
protected:
//構造和拷貝構造只聲明,不定義
ScopedPtr& operator=(const ScopedPtr & sp);
ScopedPtr(const ScopedPtr & sp);
private:
T* _ptr;
};
class AA
{
public:
int _a;
int _b;
};
//測試用例
void TestScopedPtr()
{
ScopedPtr<int> ap1(new int);
*ap1 = 10;
ScopedPtr<int> ap3(new int(10));
// ap3 = ap1;
// ScopedPtr<int> ap4(ap1);
//上述兩句代碼編譯不通過,因爲ScopedPtr的構造和拷貝構造只聲明,沒定義,
//且爲了防止別人進行修改定義爲保護或者私有成員
ScopedPtr<AA> ap2(new AA);
ap2->_a = 10;
ap2->_b = 20;
}
3>shared_ptr的模擬實現(仿函數版)(實際實現要複雜)
仿函數:
仿函數(functor),就是使一個類的使用看上去象一個函數。其實現就是類中實現一個operator(),這個類就有了類似函數的行爲,就是一個仿函數類了。
沒有使用仿函數,就不能實現SharedPtr的多種功能,只使用delete刪除的話,如果是new[]的空間,就會造成內存泄漏。
template<class T>
struct DeleteArray
{
void operator()(T *ptr)
{
delete[] ptr;
}
};
template<class T>
struct Delete
{
void operator()(T *ptr)
{
delete ptr;
}
};
struct Fclose
{
void operator()(FILE * ptr)
{
fclose(ptr);
}
};
template<class T,class Del>
class SharedPtr
{
public:
SharedPtr(T *ptr)
:_ptr(ptr)
, _reference_count(new int (1))
{
}
~SharedPtr()
{
Clear();
}
void Clear()
{
if (--(*_reference_count) == 0)
{
_del(_ptr);
delete _reference_count;
_reference_count = NULL;
}
}
SharedPtr( SharedPtr<T,Del> & sp)
{
_ptr = sp._ptr;
_reference_count = sp._reference_count;
(*_reference_count)++;
}
//傳統寫法
//SharedPtr& operator =(const SharedPtr<T,Del> & sp)
//{
// if (this != &sp)
// {
// Clear();
// _ptr = sp._ptr;
// _reference_count = sp._reference_count;
// (*_reference_count)++;
// }
// return *this;
//}
//現代寫法
SharedPtr& operator =(SharedPtr<T,Del> sp)
{
swap(_ptr, sp._ptr);
swap(_reference_count, sp._reference_count);
return *this;
}
T& operator *()
{
return *(this->_ptr);
}
T* operator->()
{
return _ptr;
}
private:
T *_ptr;
Del _del;
int *_reference_count;
};
//測試用例
void TestSharedPtr()
{
SharedPtr<int,Delete<int>> ap3(new int(10));
SharedPtr<int,Delete<int>> ap4(ap3);
SharedPtr<int,DeleteArray<int>> ap5(new int[10]);
SharedPtr<FILE, Fclose>ap6(fopen("text.txt", "w"));
}
4>weak_ptr產生原因:
shared_ptr雖然功能強大,但是存在一些問題,比如說循環引用問題。
struct ListNode
{
shared_ptr<ListNode> _prev;
//shared_ptr爲庫文件中實現的,只需包memory即可使用
shared_ptr<ListNode> _next;
int data;
ListNode(int x)
{
data = x;
_prev = NULL;
_next = NULL;
}
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
shared_ptr<ListNode> cur(new ListNode(1));
shared_ptr<ListNode> next(new ListNode(2));
cur->_next = next;
next->_prev = cur;
return 0;
}
循環引用:
爲了解決shared_ptr循環引用所帶來的問題,就有了weak_ptr:
weak_ptr是一種不控制所指對象生存週期的智能指針,它指向一個shared_ptr所管理的對象。將一個weak_ptr綁定到shared_ptr不會改變shared_ptr的引用計數。一旦最後一個指向對象的shared_ptr被銷燬,對象就會被釋放。weak_ptr的名字抓住了這種智能指針“弱”共享對象的特點。