上一篇文章介紹了智能指針的基本概念及boost庫裡基本的智能指針,這裡主要模拟實作庫裡面的智能指針(簡單實作)。https://blog.csdn.net/xu1105775448/article/details/80625936
auto_ptr
1.auto_ptr具有RAII和像指針一樣的特點。
2.模拟實作:
template<class T>
class Auto_Ptr
{
public:
//RAII
Auto_Ptr(T* ptr)
:_ptr(ptr)
{}
~Auto_Ptr()
{
delete _ptr;
}
//像指針一樣
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
};
void TestAutoPtr()
{
int *ptr = new int();
Auto_Ptr<int> p(ptr);
/**p = 20;
cout << *p << endl;*/
Auto_Ptr<int> p1(p);
}
3.但是如果隻寫上面的代碼就會有一個問題,當我們想将一個AutoPtr指派(或拷貝構造)給另一個AutoPtr,我們沒寫它的拷貝構造和指派運算符重載,就會調用編譯器的,這是淺拷貝,就會導緻空間被多次釋放。
4.auto_ptr思想是:管理權的轉移。也就是當發現有一個auto_ptr對象a1指派給另一個對象a2或者拷貝構造a2,那麼a1獲得的資源就交給a2,a2來管理a1指向的那塊空間,a1就指向NULL,這樣就保證了每塊空間隻有一個auto_ptr對象指向它,就不會有空間被釋放多次的問題。
5.自己模拟實作完善版本的:
template<class T>
class AutoPtr
{
public:
//構造函數獲得資源
AutoPtr(T* ptr)
:_ptr(ptr)
{}
//管理權轉移
AutoPtr(AutoPtr<T>& ap) //參數不能加const
:_ptr(ap._ptr)
{
ap._ptr = NULL;
}
AutoPtr<T>& operator=(AutoPtr<T>& ap)
{
if (this != &ap)
{
//先釋放自己的
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
//析構函數清理資源
~AutoPtr()
{
if (_ptr != NULL)
{
delete _ptr;
printf("0X%p\n", _ptr);
}
}
//像指針一樣
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
};
void TestAutoPtr()
{
AutoPtr<int> ap1(new int());
AutoPtr<int> ap2(new int());
ap1 = ap2;
}
6.auto_ptr管理權的另一種實作方式:(增加一個bool類型的owner,作為一個标志,如果我管理這塊空間,那麼我的owner就為true,沒有管理就為false);但是這種方式沒有上面直接将指向的空間置空好,若ap1拷貝構造ap2,那麼這裡的所有權就在ap2上,即ap2的owner為true,ap1的owner為false,但是如果ap2的生命周期比ap1短就有問題。例如函數傳參時,當用ap2傳給一個函數fun,而fun得參數為一個auto_ptr對象ap,就會将ap2得管理權轉移給ap,當ap出了作用域就會調用其析構函數,就會将ap指向的空間釋放掉,就會使得再想通路ap2就會出錯。
template<class T>
class AutoPtr
{
public:
//構造函數獲得資源
AutoPtr(T* ptr)
:_ptr(ptr)
,_owner(true)
{}
//管理權轉移
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)
{
ap._owner = false;
_owner = true;
}
AutoPtr<T>& operator=(AutoPtr<T>& ap)
{
if (this != &ap)
{
//先釋放自己的
delete _ptr;
_ptr = ap._ptr;
ap._owner = false;
_owner = true;
}
return *this;
}
//析構函數清理資源
~AutoPtr()
{
if (_owner == true)
{
delete _ptr;
printf("0X%p\n", _ptr);
}
}
//像指針一樣
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
bool _owner;
};
7.但是autoptr有個很大的缺點,當将ap1的管理權交給ap2後,就會使得ap1指向一塊空的空間,當需要解引用ap1時,就是解引用空指針。
scoped_ptr
1.scoped_ptr也要有RAII和像指針一樣的特點,它采用防拷貝的方式。(防拷貝:将拷貝構造和指派運算符是聲明實作,并且聲明為私有,因為我們自己聲明,就不會調用系統自動生成的,而且如果隻聲明,就會有人在類外定義,是以必須聲明為私有)
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;
}
public:
//将拷貝構造和指派運算符重載聲明為私有
ScopedPtr(const ScopedPtr<T>& sp);
ScopedPtr<T> operator=(const ScopedPtr<T>& sp);
protected:
T* _ptr;
};
void TestScopedPtr()
{
ScopedPtr<int> sp(new int());
ScopedPtr<int> sp1(sp);
}
scoped_array
1.scoped_ptr用于管理new單個對象的空間的釋放,而scoped_array用于管理new多個對象的釋放(即調用new[]),而且scoped_array裡面不支援operator*和operator->。對資料的通路采用[](像數組一樣,通過下标的形式)。
2.模拟實作:
template<class T>
class ScopedArray
{
public:
//RAII
ScopedArray(T* ptr)
:_ptr(ptr)
{}
~ScopedArray()
{
if (_ptr)
{
delete[] _ptr;
}
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
public:
ScopedArray(const ScopedArray<T>& sp);
ScopedArray<T> operator=(const ScopedArray<T>& sp);
protected:
T* _ptr;
};
void TestScopedArray()
{
ScopedArray<int> sp1(new int[]);
}
shared_ptr
1.shared_ptr為共享指針,意味我們共同指向一塊空間;裡面采用引用計數,當有别的shared_ptr指向我這塊空間時,就增加引用計數,當引用計數減為0的時候,才釋放這塊空間。
2.模拟實作:
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pCount(new int())
{}
SharedPtr(const SharedPtr<T>& ap)
:_ptr(ap._ptr)
, _pCount(ap._pCount)
{
++(*_pCount);
}
SharedPtr<T>& operator=(const SharedPtr<T>& ap)
{
if (_ptr != ap._ptr)
{
if (--(*_pCount) == )
{
delete _ptr;
delete _pCount;
}
_ptr = ap._ptr;
_pCount = ap._pCount;
++(*_pCount);
}
return *this;
}
~SharedPtr()
{
if (--(*_pCount) == )
{
delete _ptr;
delete _pCount;
cout << "~SharedPtr()" << endl;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
int* _pCount;
};
void TestSharedPtr()
{
SharedPtr<int> sp1(new int());
SharedPtr<int> sp2(sp1);
sp1 = sp2;
}
3.但是shared_ptr會出現一個問題,就是循環引用,就會導緻引用計數一直無法減到0,就會導緻空間沒有釋放。例如如下代碼:
template<class T>
class SharedPtr;
template<class T>
struct ListNode
{
T _data;
SharedPtr<ListNode<T>> _next;
SharedPtr<ListNode<T>> _prev;
ListNode(T data)
:_data(data)
, _next(NULL)
, _prev(NULL)
{}
};
//中間部分與上面SharedPtr代碼一樣
void TestSharedPtr()
{
SharedPtr<ListNode<int>> sp1(new ListNode<int> (10));
SharedPtr<ListNode<int>> sp2(new ListNode<int>(20));
sp1->_next = sp2;
sp2->_prev = sp1;
}
分析:
- 有兩個share_ptr對象sp1和sp2,剛開始sp1和sp2的引用計數都為1;
- 将sp2指派給sp1->_next的時候,sp1->_next的引用計數變為2且sp2的引用計數也變為2;将sp1指派給sp2->_prev時,sp2->_prev的引用計數變為2且sp1的引用計數也變為2。
- 又因為sp1裡的_next和_prev兩個指針的釋放依賴于sp1,而sp1的釋放又依賴于sp2->_prev,sp2的_prev的釋放又依賴于sp2,sp2又依賴于sp1的_next的釋放,這就是我的釋放依賴于你,你的釋放依賴于我,就會導緻陷入死循環中,導緻空間沒有被釋放,進而就會産生記憶體洩露的問題。 4.shared_ptr采用weak_ptr解決循環引用的問題,将會出現循環引用的shared_ptr通過weak_ptr的構造函數儲存下來,并且weak_ptr裡面不增加引用計數。但是它不是嚴格意義上的智能指針,它是輔助shared_ptr的使用,為了解決shared_ptr循環引用的問題。
template<class T>
class WeakPtr
{
public:
WeakPtr(SharedPtr<T> sp)
:ptr(sp._ptr)
{}
WeakPtr(const WeakPtr<T>& sp)
:ptr(sp.ptr)
{}
WeakPtr<T>& operator=(const WeakPtr<T>& wp)
{
if (this != &wp)
{
ptr = wp.ptr;
}
return *this;
}
protected:
T* ptr;
};
shared_array
1.shared_ptr用于管理單個對象的釋放,而shared_array用于管理多個對象的釋放。對于scoped_array來說,它不支援*和->,它支援[]的形式通路。
2.模拟實作:
template<class T>
class SharedArray
{
public:
SharedArray(T* ptr)
:_ptr(ptr)
, _pCount(new int())
{}
SharedArray(const SharedArray<T>& sa)
:_ptr(sa._ptr)
, _pCount(sa._pCount)
{}
SharedArray<T>& operator=(const SharedArray<T>& sa)
{
if (_ptr != sa._ptr)
{
if (--(*_pCount) == )
{
delete[] _ptr;
delete _pCount;
}
_ptr = sa._ptr;
_pCount = sa._pCount;
++(*_pCount);
}
return *this;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
~SharedArray()
{
if (--(*_pCount) == )
{
delete[] _ptr;
delete _pCount;
}
}
protected:
T *_ptr;
int* _pCount;
};
定制删除器
1.對于C++11來說,它的庫裡面不包含shared_array和scoped_array,是以當new[]時,必須要依靠别的方式才能按照delete[]的形式釋放。C++11和boost裡面都包含定址删除器,當我以何種方式動态開辟的空間,我就要以什麼樣的方式去釋放空間。(對于new[]就要delete[],對于fopen就要fclose,對于lock就要unlock)
2.當自己模拟實作的時候,對于shared_ptr來說,可以給它兩個模闆參數,一個表示指針的類型,一個表示采用何種方式删除。
3.定址删除器就是一個仿函數,通過函數對象調用重載的operator()函數,裡面是delete,就調用delete,裡面是delete[]就是delete[]。
4.模拟實作:
(1)首先包含仿函數,這裡定義了3個仿函數,一個是專門用來進行delete,一個用來delete[],一個用來fclose;
template<class T>
struct Delete
{
void operator()(T* ptr)
{
delete ptr;
}
};
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
delete[] ptr;
}
};
struct Fclose
{
void operator()(FILE* fp)
{
fclose(fp);
}
};
(2)前面實作的SharedPtr進一步改進,增加一個模闆參數,用來管理指針的釋放。增加一個模闆參數D,給一個預設參數為Delete,當我們不适用第二個模闆參數表示采用delete的方式,當我們給定第二個模闆參數之後,我們使用哪個模闆參數,就調用哪個仿函數。
template<class T,class D = Delete<T>>
class SharedPtr
{
friend class WeakPtr<T>;
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pCount(new int())
{}
SharedPtr(T* ptr, D d)
:_ptr(ptr)
, _d(d)
, _pCount(new int())
{}
SharedPtr(const SharedPtr<T,D>& ap,D d)
:_ptr(ap._ptr)
, _pCount(ap._pCount)
, _d(d)
{
++(*_pCount);
}
SharedPtr<T, D>& operator=(const SharedPtr<T, D>& ap)
{
if (_ptr != ap._ptr)
{
if (--(*_pCount) == )
{
_d(_ptr); //_d為仿函數對象,通過()就可以調用對應的函數
delete _pCount;
}
_ptr = ap._ptr;
_pCount = ap._pCount;
++(*_pCount);
}
return *this;
}
~SharedPtr()
{
if (--(*_pCount) == )
{
if (_ptr)
{
_d(_ptr);
delete _pCount;
cout << "~SharedPtr()" << endl;
}
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
int* _pCount;
D _d;
};
void TestSharedPtr()
{
DeleteArray<string> da;
SharedPtr<string,DeleteArray<string>> sp(new string[],da);
SharedPtr<int> sp1(new int());
SharedPtr<string, DeleteArray<string>> sp2(new string[]);
/*SharedPtr<FILE, Fclose> sp2(fopen("text.txt", "r"), Fclose());
SharedPtr<FILE, Fclose> sp3(fopen("text.txt", "w"));*/
}