【C++】智慧指標
阿新 • • 發佈:2018-12-26
本篇博文旨在介紹C++中的智慧指標;從為什麼引入它開始,分別實現了auto_ptr,scoped_ptr,unique_ptr,shared_ptr等智慧指標;介紹了各個智慧指標的特點;最後用防函式和智慧指標實現了檔案指標的管理
智慧指標概念
智慧指標是一個類,用它的物件管理著申請的記憶體空間,並通過作用域、生命週期來保證申請記憶體的釋放,從而防止出現記憶體洩漏
記憶體洩漏舉例
在上述程式碼中,如果檔案開啟失敗,那麼p指標就沒有進行釋放,會造成記憶體洩漏#include<iostream> using namespace std; //為什麼需要智慧指標 //每次返回都需要對指標進行處理 //很是麻煩 //所以要引出智慧指標,可以自出進行記憶體的釋放 void FunTest() { int* p = new int[10]; FILE* pFile = fopen("1.txt", "r"); if (pFile == NULL)//如果開啟失敗,那麼p就成為了一個野指標 { return; } if (p != NULL) { delete[] p; p = NULL; } }
如果我們每次在返回語句前進行釋放,當前面new申請的空間很多時,會不會太麻煩了
智慧指標
1、RAII
RAII稱為“資源獲取就是初始化”,是c++等程式語言常用的管理資源
避免記憶體洩露的方法。它保證在任何情況下,
使用物件時先構造物件,最後析構物件。
2、boost庫
瞭解智慧指標就需要了解boost庫,C++11引入的智慧指標標準中,參考了boost庫中對智慧指標的實現
模擬實現智慧指標
1、autoptr——不推薦使用的智慧指標
auto_ptr的缺陷:template<typename T> class AutoPtr { public: AutoPtr(T* ap = NULL) :_p(ap) { ap = NULL; cout << "AutoPtr()" << endl; } AutoPtr(AutoPtr& ap) :_p(ap._p) { ap._p = NULL; //ap = NULL; } AutoPtr& operator=(AutoPtr&ap) { if (this != &ap) { if (_p != NULL) { delete _p; _p = ap._p; ap._p = NULL; } } return *this; } ~AutoPtr() { if (_p != NULL) { cout << "~AutoPtr()" << endl; delete[] _p; _p = NULL; } } T* Get() { return _p; } T& operator*() { return *_p; } T* operator->() { return _p; } private: T* _p; };
只能由一個物件來管理空間
當多個auto_ptr指向同一塊空間時,會由於多次釋放而導致崩潰
2、ownerptr——設定布林變數來表示有沒有管理空間
當進行拷貝構造,進行管理許可權的交換
只有掌握空間許可權的才可以進行釋放
template <typename T> class OwnerPtr { public: //建構函式 OwnerPtr(T* ap = NULL) :_p(ap) , _owner(true) { cout << "OwnerPtr()" << endl; } //拷貝建構函式 OwnerPtr(OwnerPtr<T>& ap) :_p(ap._p) , _owner(true) { ap._owner = false; } OwnerPtr<T>& operator=(OwnerPtr<T>& ap) { if (this != &ap) { if (_p != NULL) { delete _p; _p = ap._p; _owner = true; ap._owner = false; } } return *this; } ~OwnerPtr() { if (_owner && _p != NULL) { cout << "~OwnerPtr()" << endl; delete _p; _p = NULL; _owner = false;//false代表釋放完畢 } } T* Get() { return _p; } T* operator*() { return *_p; } T* operator->() { return _p; } private: T* _p; bool _owner;//表示物件是否佔用資源 };
3、scopedptr——最容易實現的智慧指標
scoped_ptr直接不允許進行拷貝以及賦值運算子過載,故而沒有釋放兩次導致崩潰的事情發生
template<typename T>
class ScopedPtr
{
public:
ScopedPtr(T* ap = NULL)
:_p(ap)
{}
~ScopedPtr()
{
if (_p != NULL)
{
delete _p;
_p = NULL;
}
}
T* Get()
{
return _p;
}
T& operator*()
{
return *_p;
}
T* operator->()
{
return _p;
}
private:
ScopedPtr(const ScopedPtr& ap);
ScopedPtr<T>& operator=(const ScopedPtr& ap);
//下面是另一種防止拷貝的方法
/*ScopedPtr(const ScopedPtr& ap) = delete;
ScopedPtr<T>& operator=(const ScopedPtr& ap) = delete;*/
protected:
T* _p;
};
4、uniqueptr——類似於一個動態開闢的陣列
由於可以用vector實現,所以用的不多
並且不存在於boost庫
//unique_ptr
//過載了[]
//就比如一個vecotr開闢的動態陣列
template<typename T>
class UniquePtr
{
public:
UniquePtr(T* ap)
:_p(ap)
{}
T& operator[](const size_t index)
{
return _p[index];
}
const T& operator[](const size_t index)const
{
return _p[index];
}
T* Get()
{
return _p;
}
protected:
T* _p;
};
void Funtest()
{
UniquePtr<int> p(new int[5]);
p[0] = 1;
p[1] = 2;
p[2] = 3;
p[3] = 4;
p[4] = 5;
for (size_t i = 0; i<5; i++)
{
cout << p[i] << " ";
}
}
5、sharedptr——引用計數版本的只能指標
通過對管理一塊空間的智慧指標進行計數,只當_pCount == 0 的時候再進行釋放
否則就 --_pCount
template<typename T>
class SharedPtr
{
public:
SharedPtr(T* _ap)
:_p(ap)
, _pCount(NULL)
{
//當物件不為空時,對_pCount進行引用計數統計
if (_p != NULL)
{
_pCount = new int[1];
}
}
SharedPtr(SharedPtr<T> &ap)
:_p(ap._p)
, _pCount(ap._pCount)
{
//如果物件不為空
//則增加引用計數
if (p != NULL)
{
++(*_pCount);
}
sp._p = NULL;
}
SharedPtr<T> operator=(SharedPtr<T> &ap)
{
if (this != &ap)
{
//沒有管理空間
if (_pCount == NULL)
{
_p = ap._p;
_pCount = ap._pCount;
if (_pCount != NULL)
++(*_pCount);
}
else if (*_pCount == 1)//獨自管理一段空間
{
delete _p;
delete _pCount;
_p = ap._p;
_pCount = ap._pCount;
if (_pCount != NULL)
++(*_pCount);
}
else//和別人共享空間
{
--(*_pCount);
_p = ap._p;
_pCount = ap._pCount;
if (_pCount != NULL)
++(*_pCount);
}
}
return *this;
}
protected:
T* _p;
int* _pCount;
};
6、sharedptr的迴圈引用解決方法---weakptr
sharedptr的迴圈引用
template<typename T>
class Node
{
public:
Node(const T& value)
: _value(value)
{
cout << "Node()" << endl;
}
~Node()
{
cout << "~Node()" << endl;
cout << "this:" << this << endl;
}
shared_ptr<Node<T>> _pNext;
shared_ptr<Node<T>> _pPre;
T _value;
};
void TestSharedPtr()
{
shared_ptr<Node<int>> s1(new Node<int>(1));
shared_ptr<Node<int>> s2(new Node<int>(2));
cout << "s1-use_count:" << s1.use_count() << endl;
cout << "s2-use_count:" << s2.use_count() << endl;
s1->_pNext = s2;
s2->_pPre = s1;
cout << "s1-use_count:" << s1.use_count() << endl;
cout << "s2-use_count:" << s2.use_count() << endl;
}
我們看到,當 s1->_pNext = s2; s2->_pPre = s1; 時
他們各自的引用計數會自增
這樣就會導致釋放的時候 use_count -1 = 1;並不會釋放
就造成了記憶體洩漏
利用weakptr解決迴圈引用問題
template<typename T>
class Node
{
public:
Node(const T& value)
: _value(value)
{
cout << "Node()" << endl;
}
~Node()
{
cout << "~Node()" << endl;
cout << "this:" << this << endl;
}
weak_ptr<Node<T>> _pNext;
weak_ptr<Node<T>> _pPre;
T _value;
};
void TestSharedPtr()
{
shared_ptr<Node<int>> s1(new Node<int>(1));
shared_ptr<Node<int>> s2(new Node<int>(2));
cout << "s1-use_count:" << s1.use_count() << endl;
cout << "s2-use_count:" << s2.use_count() << endl;
s1->_pNext = s2;
s2->_pPre = s1;
cout << "s1-use_count:" << s1.use_count() << endl;
cout << "s2-use_count:" << s2.use_count() << endl;
}
實現檔案指標釋放的兩種該方法
1、函式指標
//利用函式實現檔案指標的關閉
void FClose(FILE* pf)
{
if (pf)
fclose(pf);
}
void FunTest2()
{
FILE* p = fopen("1.txt", "r");
shared_ptr<FILE> sp(p, FClose);
}
2、利用防函式定製刪除器
//利用防函式來控制檔案指標的關閉
class FClose//定製刪除器
{
void operator()(FILE* p)
{
if (p)
fclose(p);
}
};
void FunTest3()
{
FILE* p = fopen("1.txt", "r");
shared_ptr<FILE> sp(p, FClose);
}