智慧指標(下)-----boost庫智慧指標,定製刪除器、迴圈引用
上一篇我們已經詳細講解了智慧指標的基礎性知識和auto_ptr的模擬實現。
今天呢我們來講解boost庫的發展。
在C++11標準出來之前,C++98標準中都一直只有一個智慧指標auto_ptr,我們知道,這是一個失敗的設計。它的本質是管理權的轉移,這有許多問題。而這時就有一群人開始擴充套件C++標準庫的關於智慧指標的部分,他們組成了boost社群,他們負責boost庫的開發和維護。其目的是為C++程式設計師提供免費、同行審查的、可移植的程式庫。boost庫可以和C++標準庫完美的共同工作,並且為其提供擴充套件功能。現在的C++11標準庫的智慧指標很大程度上“借鑑”了boost庫。
1,scoped_ptr的模擬實現
scoped_ptr是一種簡單粗暴的設計,它本質就是防拷貝,避免出現管理權的轉移。這是它的最大特點,所以他的拷貝構造和賦值運算子過載只是只宣告不定義,但是為了防止有的人在類外定義,所以將函式宣告為protected。但這則也是他最大的問題所在,就是不能賦值拷貝,也就是說功能不全。
template<class T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr = NULL)
:_ptr(ptr)
{
cout << "ScopedPtr" << endl;
}
~ScopedPtr()
{
delete _ptr;
cout << "~ScopedPtr" << endl;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
bool operator==(const ScopedPtr<T>& s)
{
return _ptr == s._ptr;
}
bool operator!=(const ScopedPtr<T>& s)
{
return _ptr != s._ptr;
}
void Reset(T* ptr = NULL)
{
if (_ptr != ptr)
{
delete _ptr;
}
_ptr = ptr;
}
protected:
ScopedPtr(ScopedPtr<T>& s); //防拷貝(只宣告不定義,為防止別人在類外定義,就將他宣告為protected)
ScopedPtr<T>& operator=(ScopedPtr<T>& s);
protected:
T* _ptr;
};
2,shared_ptr的模擬實現
這是比較完善的一個智慧指標,他是通過指標保持某個物件的共享擁有權的智慧指標。若干個shared_ptr物件可以擁有同一個物件,該物件通過維護一個引用計數,記錄有多少個shared_ptr指標指向該物件,最後一個指向該物件的shared_ptr被銷燬或重置時,即引用計數變為0時,該物件被銷燬。銷燬物件時使用的是delete表示式或是在構造shared_ptr時傳入的自定義刪除器(delete),這後面會有詳細講解,但是shared_ptr指標同樣擁有缺陷,那就是迴圈引用,和執行緒安全問題,這也在後面講解。先來模擬實現一下shared_ptr指標。
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = NULL)
:_ptr(ptr),_count(new int(0))
{
if (_ptr != NULL)
{
(*_count)++;
}
}
SharedPtr(const SharedPtr<T>& s)
{
_ptr = s._ptr;
_count = s._count;
if (_ptr != NULL)
{
(*_count)++;
}
}
SharedPtr<T>& operator=(const SharedPtr<T>& s)
{
if (this != &s) //排除自己給自己賦值
{
if (--(*_count) <= 0) //正常情況賦值
{
delete _ptr;
delete _count;
}
else //指向同一個物件的指標互相賦值
{}
_ptr = s._ptr;
_count = s._count;
(*_count)++;
}
return *this;
}
~SharedPtr()
{
if (--(*_count) == 0)
{
delete _ptr;
delete _count; //別忘了delete維護的引用計數
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
bool operator ==(const SharedPtr<T>& S)
{
return (_ptr == s._ptr);
}
bool operator !=(const SharedPtr<T>& S)
{
return (_ptr != s._ptr);
}
protected:
T* _ptr;
int* _count;
};
執行緒安全問題
因為使用引用計數值位判定指標,所以在多執行緒的環境下是不安全的。會因執行緒呼叫的先後順序不同導致錯誤產生。對於這種問題,解決方法一般是加鎖,對引用計數進行加鎖,保證操作是互斥的。(這裡暫且說這些,後續文章會有提及。)
迴圈引用問題
針對迴圈引用,我來舉個例子使大家能更好的理解。看下面程式碼:
struct ListNode
{
int _data;
shared_ptr<ListNode> _next;
shared_ptr<ListNode> _prev;
ListNode(int x)
:_data(x),_next(NULL),_prev(NULL)
{}
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
void test()
{
shared_ptr<ListNode> A(new ListNode(1));
shared_ptr<ListNode> B(new ListNode(2));
//if (A && B) //將這五行程式碼放開來會出現什麼情況
//{
// A->_next = B;
// B->_prev = A;
//}
cout << "A._count:" << A.use_count() << endl;
cout << "B._count:" << B.use_count() << endl;
}
int main()
{
test();
system("pause");
return 0;
}
執行結果
但是如果我將上面程式碼中遮蔽的那五行放開來,會出現什麼結果呢?看下圖:
有什麼不同?對比上圖,可以發現下面圖的兩個節點維護的引用計數值為2,他們也沒有呼叫解構函式造成記憶體洩露。這是什麼原因造成的?我們用一張圖來解釋。
而要解決迴圈引用的問題,就牽扯到了我們後面將要講的一個指標weak_ptr。具體看後面。
定製刪除器(仿函式)
經上面分析,我們可以看到,上面的指標不能用於檔案的關閉,也不能用於管理malloc和new[]開闢的動態記憶體的釋放,所以我們可以運用仿函式來定製刪除器。如下:
template<class T>
struct DeleteArray //用於new[]開闢的動態記憶體釋放
{
void operator()(T* ptr)
{
cout << "A" << endl;
delete[] ptr;
}
};
struct Fclose //用於檔案關閉
{
void operator()(FILE* ptr)
{
cout << "B" << endl;
fclose(ptr);
}
};
template<class T>
struct Free //用於malloc開闢的動態記憶體的釋放
{
void operator()(T* ptr)
{
cout << "C" << endl;
free(ptr);
}
};
int main()
{
shared_ptr<string> ap1(new string[10], DeleteArray<string>());
shared_ptr<FILE> ap2(fopen("test.txt", "w"),Fclose());
shared_ptr<int> ap3((int*)malloc(sizeof(int)), Free<int>());
return 0;
}
3,weak_ptr
weak_ptr是一個輔助性的智慧指標,結合shared_ptr指標使用,它的本質就是弱引用,並不增加引用計數值。他沒有實現->和*運算子的過載,所以不能直接用它訪問物件。針對迴圈引用這個問題,就是因為不會引起引用計數值的改變,所以我們可以將_next和_prev定義為weak_ptr指標,這樣就很好地解決了迴圈引用的問題。
struct ListNode
{
int _data;
weak_ptr<ListNode> _next; //定義為weak_ptr指標
weak_ptr<ListNode> _prev;
ListNode(int x)
:_data(x),_next(NULL),_prev(NULL)
{}
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
boost庫剩餘的兩個指標:auto_arr和shared_arr.這兩個都是管理陣列的,因為之前幾個指標的解構函式中都是delete,不能對陣列進行釋放,所以我們自己定製刪除器,而這兩個指標就是專門管理指標的。下面來模擬實現一下。
模擬實現auto_arr
template<class T>
class AutoArr
{
public:
AutoArr(T* ptr = NULL)
:_ptr(ptr)
{}
~AutoArr()
{
delete[] _ptr;
}
AutoArr(const AutoArr<T>& s)
{
_ptr = s._ptr;
s._ptr = NULL;
}
AutoArr<T>& operator=(const AutoArr<T>& s)
{
if (this != &s)
{
_ptr = s._ptr;
s._ptr = NULL;
}
return *this;
}
T& operator[](size_t pos)
{
if (_ptr == NULL)
{
throw a;
}
return *(_ptr+pos);
}
void set(T* ptr)
{
int i = 0;
while (*(ptr + i))
{
*(_ptr + i) = *(ptr + i);
i++;
}
}
protected:
T* ptr;
};
模擬實現shared_arr
template<class T>
class SharedArr
{
public:
SharedArr(T* ptr = NULL)
:_ptr(ptr),_count(new int(0))
{
(*_count)++;
}
~SharedArr()
{
delete[] _ptr;
}
SharedArr(const SharedArr<T>& s)
{
_ptr = s._ptr;
(*_count)++;
}
SharedArr<T>& operator=(const SharedArr<T>& s)
{
if (this != &s)
{
if (--(*_count) <= 0)
{
delete _ptr;
delete _count;
}
else
{ }
_ptr = s._ptr;
_count = s._count;
(*_count)++;
}
}
T& operator[](size_t pos)
{
if (_ptr == NULL)
{
throw 1;
}
return *(_ptr + pos);
}
protected:
T* _ptr;
int* _count;
};