爲什麼要有智能指針?
在C++中沒有垃圾回收機制,用戶需要自己來對已經申請的內存空間進行釋放,如果不釋放會存在內存泄漏問題。如果代碼拋異常,資源沒有回收也可能產生死鎖。
智能指針的使用
首先介紹一下RAII,它是Resource Acquisition Is Initialization,即資源獲取時就初始化,將資源使用方式用類的方式來進行封裝。在構造函數中將資源進行初始化,在析構函數中清理資源。
RAII是一種利用對象生命週期來控制程序資源(如內存,文件句柄,網絡連接,互斥量等等)的簡單技術。
在對象構造時獲取資源,接着控制對資源的訪問使之在對象的生命週期內始終保持有效,最後在對象析構的時候釋放資源。藉此,實際上是把管理一份資源的責任託管給了一個對象。
兩大好處:
- 不需要顯示地釋放資源。
- 採用這種方式,對象所需要的資源在其生命週期內始終保持有效。
智能指針的實現原理
1. 實現RAII思想
2. 像指針一樣 operator() operator->()
3. 解決深淺拷貝*
auto_ptr
namespace a{
template<class T>
class auto_ptr{
public:
auto_ptr(T* ptr=nullptr)
:_ptr(ptr)
{}
~auto_ptr(){
if (_ptr)
{
delete _ptr;
}
}
auto_ptr(auto_ptr<T>& p)
{
_ptr = p._ptr;
p._ptr = nullptr;
}
//實現RAII 思想,像指針一樣,解決深淺拷貝問題
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr;
};
}
struct Date{
int _year;
int _month;
int _day;
};
int main()
{
a::auto_ptr<int> p1(new int(1));
*p1=2;
a::auto_ptr<int> p2(p1);
//auto_ptr 的拷貝構造,是將資源進行轉移,當下次繼續對p1進行* 引用時,就會出錯!
*p1 = 2;
a::auto_ptr<Date> p3(new Date);
return 0;
}
標準庫中建議無論什麼情況下都不要使用auto_ptr。
unique_ptr
由上面的代碼可知,auto_ptr 存在對已經釋放的資源解引用會出錯,則C++11提供了一種unique_ptr 它顧名思義,防止拷貝與賦值。就防止了資源的泄漏。
namespace b{
template<class T>
class unique_ptr{
public:
unique_ptr(T* ptr=nullptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
delete _ptr;
}
}
//像指針一樣
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
//c++11中防止拷貝的方式
//函數後面加上delete
//意思是禁止該函數生成,將默認成員函數刪除
unique_ptr(const unique_ptr<T> &) = delete;
unique_ptr<T>& operator=(unique_ptr<T>&)=delete
private:
//防止拷貝與防止運算符重載
//c++98在私有中只聲明不定義
//unique_ptr(const unique_ptr<T>& p);
//unique_ptr<T>& operator = (unique_ptr<T> p);
private:
T* _ptr;
};
}
struct Data{
int _date;
int _year;
int _month;
};
int main()
{
b::unique_ptr<int> p1(new int(1));
//b::unique_ptr<int> p2(p1);
//b::unique_ptr<int> p3(new int(3));
//p1 = p3;
b::unique_ptr<Data> p2(new Data);
p2->_date = 1;
p2->_month = 2;
p2->_year = 3;
return 0;
}
share_ptr
支持拷貝,通過引用計數的方式來實現多個share_ptr對象之間共享資源
- share_prt 在其內部,給每個資源都維護了一份計數,來記錄該份資源被幾個對象共享。
- 在對象被銷燬時(也就是析構函數調用),就說明自己不使用該資源了,對象的引用計數減一。
- 如果引用計數是0,就說明自己還是最後一個使用該資源的對象,就必須釋放該資源
- 如果引用計數不是0,就說明除了自己還要其他對象在使用該份資源,不能釋放該資源,否則其他對象就成爲野指針了。
#include<mutex>
namespace c{
template<class T>
class share_ptr{
public:
share_ptr(T* ptr=nullptr)
:_ptr(ptr)
, _pCount(nullptr)
, _mt(nullptr)
{
if (_ptr)
{
_pCount = new int(0);
_mt = new mutex;
++*_pCount;
}
}
~share_ptr()
{
_mt->lock();
if (_ptr&&--*_pCount==0)
{
delete _ptr;
}
_mt->unlock();
}
share_ptr(const share_ptr<T>& p)
:_ptr(p._ptr)
, _pCount(p._pCount)
{
*_pCount++;
}
share_ptr<T>& operator=(share_ptr<T>& p)
{
//賦值分情況
if (this != &p)
{
_mt->lock();
//被賦值的對象
if (_ptr)
{
if (_ptr&&--*_pCount == 0)
{
delete _ptr;
delete _pCount;
}
}
_ptr = p._ptr;
_pCount = p._pCount;
++*_pCount;
_mt->unlock();
}
return *this;
}
//像指針一樣
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pCount;
mutex* _mt;
};
}
struct Date{
int _day;
int _year;
int _month;
};
int main()
{
c::share_ptr<int> p1(new int(1));
c::share_ptr<int> p2(new int(2));
c::share_ptr<int> p3;
p3 = p1;
p2 = p1;
return 0;
}
上述代碼存在缺陷,1.++與–存在線程安全 2.不能管理任意類型的指針 3.循環引用
什麼是循環引用?
當用share_ptr 定義雙向鏈表的節點時,當兩個節點相互指向,此時兩個節點的引用計數都爲2,在兩個節點都釋放時,引用計數的值爲1,此時兩個節點還是相互指向,沒有釋放,形成了循環引用。
怎麼解決循環引用?
weak_ptr,將兩個節點的_pre,_next的指針用weak_ptr來定義,注意,weak_ptr必須搭配share_ptr來使用。
怎麼解釋線程不安全?
加鎖
解決管理任意類型的指針,構造刪除器!
//任意類型的資源能夠進行管理
//假設:資源可以new,malloc 文件指針,套接字
//定製刪除器
//針對new的資源
template<class T>
struct DFDel
{
void operator()(T*& ptr)
{
if (ptr)
{
delete ptr;
ptr == nullptr;
}
}
};
//對於文件資源
struct FClose{
void operator()(FILE* pf)
{
if (pf)
{
fclose(pf);
}
}
};
//針對free的資源
template<class T>
struct Free
{
void operator()(T*& ptr)
{
if (ptr)
{
free(ptr);
ptr = nullptr;
}
}
};
namespace b{
template<class T, class DF = DFDel<T>>
class unique_ptr{
public:
unique_ptr(T* ptr = nullptr)
:_ptr(ptr)
{}
~unique_ptr()
{
DF(_ptr);
}
//像指針一樣
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
//c++11中防止拷貝的方式
//函數後面加上delete
//意思是禁止該函數生成,將默認成員函數刪除
unique_ptr(const unique_ptr<T> &) = delete;
unique_ptr<T>& operator=(unique_ptr<T>&) = delete
private:
//防止拷貝與防止運算符重載
//c++98在私有中只聲明不定義
//unique_ptr(const unique_ptr<T>& p);
//unique_ptr<T>& operator = (unique_ptr<T> p);
private:
T* _ptr;
};
}
struct Data{
int _date;
int _year;
int _month;
};
int main()
{
b::unique_ptr<int> p1(new int(1));
//b::unique_ptr<int> p2(p1);
//b::unique_ptr<int> p3(new int(3));
//p1 = p3;
b::unique_ptr<Data> p2(new Data);
p2->_date = 1;
p2->_month = 2;
p2->_year = 3;
b::unique_ptr<Data,Free<Data>> p4((Data*)malloc(sizeof(Data)));
b::unique_ptr<char, Free<char>> p5((char*)malloc(sizeof(char)));
return 0;
}
以unique_ptr 爲例構造刪除器!