RAII是Resource Acquisition Is Initialization的簡稱,是C++語言的一種管理資源、避免洩漏的慣用法。
RAII又叫做資源分配即初始化,即:定義一個類來封裝資源的分配和釋放,在構造函數完成資源的分配和初始化,在析構函數完成資源的清理,可以保證資源的正確初始化和釋放。
第一步和第三步缺一不可,因為資源必須要申請才能使用的,使用完成以後,必須要釋放,如果不釋放的話,就會造成資源洩漏。
所謂智能指針就是智能/自動化的管理指針所指向的動態資源的釋放。它是一個類,有類似指針的功能。
包括:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost::intrusive_ptr
Boost庫的智能指針(ps:新的C++11標准中已經引入了unique_ptr/shared_ptr/weak_ptr):
第一種實現:最開始auto_ptr的成員變量主要有T* _ptr 和 bool _owner,主要實現原理是在構造對象時賦予其管理空間的所有權,在拷貝或賦值中轉移空間的所有權,在析構函數中當_owner為true(擁有所有權)時來釋放所有權。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57template
<
typename
T>
class
AutoPtr
{
public
:
//構造函數
explicit
AutoPtr(T* ptr = NULL)
:_ptr(ptr)
, _owner(
true
)
{}
//拷貝構造
AutoPtr(AutoPtr<T>& ap)
//參數不能寫成const的,這裡要修改ap對象的成員<br> :_ptr(ap._ptr)
, _owner(
true
)
{
ap._owner =
false
;
//轉讓權限
}
//賦值運算符重載
AutoPtr& operator=(AutoPtr<T>& ap)
{
if
(
this
! = &ap)
{
delete
this
->_ptr;
this
->_ptr = ap._ptr;
// 轉讓權限
this
->_owner =
true
;
ap._owner =
false
;
}
return
*
this
;
}
//析構函數
~AutoPtr()
{
// 只刪除擁有權限的指針
if
(_owner)
{
this
->_owner =
false
;
delete
this
->_ptr;
}
}
T& operator*()
{
return
*(
this
->_ptr);
}
T* operator->()
{
return
this
->_ptr;
}
T* AutoPtr<T>::GetStr()
{
return
(
this
->_ptr);
}
protected
:
T* _ptr;
bool
_owner;
//權限擁有者
};
出現的主要問題:如果拷貝出來的對象比原來的對象先出作用域或先調用析構函數,則原來的對象的_owner雖然為false,但卻在訪問一塊已經釋放的空間,原因在於拷貝對象的釋放會導致原對象的_ptr指向的內容跟著被釋放!
問題體現如下:
ap1將析構的權限給予了ap2,由於ap1._ptr和ap2._ptr指向同一份空間,ap2在出了if作用域之後自動被釋放,進而導致ap1._ptr也被釋放。
但是在if作用域之外,又對ap1._ptr進行訪問,導致程序崩潰,這時候 ap1._ptr已經是一個野指針了,這就造成指針的懸掛的問題!
1 2 3 4 5 6 7 8 9 10 11void
Test()
{
AutoPtr<
int
> ap1(
new
int
(1));
if
(
true
)
{
AutoPtr<
int
> ap2(ap1);
}
//這裡的ap1._ptr已經是一個野指針了,這就造成指針的懸掛的問題
*(ap1.GetStr() )= 10;
}
auto_ptr的第二種實現方法:還是管理空間的所有權轉移,但這種實現方法中沒有_owner權限擁有者。構造和析構和上述實現方法類似,但拷貝和賦值後直接將_ptr賦為空,禁止其再次訪問原來的內存空間,比較簡單粗暴。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52template
<
typename
T>
class
AutoPtr
{
public
:
//構造函數
explicit
AutoPtr(T* ptr = NULL)
//不能寫成const T* ptr,否則出現const類型賦值給非const類型的問題
:_ptr(ptr)
{}
//拷貝構造
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr=NULL;
}
//賦值運算符重載
AutoPtr& operator=(AutoPtr<T>& ap)
{
if
(
this
! = &ap)
{
delete
this
->_ptr;
this
->_ptr = ap._ptr;
ap._ptr = NULL;
}
return
*
this
;
}
//析構函數
~AutoPtr()
{
// 只刪除擁有權限的指針
if
(_ptr)
{
delete
_ptr;
}
}
T& operator*()
{
return
*_ptr;
}
T* operator->()
{
return
_ptr;
}
T* GetStr()
{
return
_ptr;
}
protected
:
T* _ptr;
};
這種實現方式很好的解決了舊版本野指針問題,但是由於它實現了完全的權限轉移,所以導致在拷貝構造和賦值之後只有一個指針可以使用,而其他指針都置為NULL,使用很不方便,而且還很容易對NULL指針進行解引用,導致程序崩潰,其危害也是比較大的。
scoped_ptr的實現原理是防止對象間的拷貝與賦值。具體實現是將拷貝構造函數和賦值運算符重載函數設置為保護或私有,並且只聲明不實現,並將標志設置為保護或私有,防止他人在類外拷貝,簡單粗暴,但是也提高了代碼的安全性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37template
<
typename
T>
class
ScopedPtr
{
public
:
ScopedPtr(T* ptr = NULL)
:_ptr(ptr)
{}
T& operator*()
{
return
*_ptr;
}
T* operator->()
{
return
_ptr;
}
T* GetStr()
{
return
_ptr;
}
//析構函數
~ScopedPtr()
{
if
(_ptr!=NULL)
{
delete
_ptr;
}
}
protected
:
//防拷貝
ScopedPtr(ScopedPtr<T>& ap);
ScopedPtr& operator=(ScopedPtr<T>& ap);
T* _ptr;
};
scoped_ptr的實現和auto_ptr非常類似,不同的是 scoped_ptr有著更嚴格的使用限制——不能拷貝,這就意味著scoped_ptr 是不能轉換其所有權的。
在一般的情況下,如果不需要對於指針的內容進行拷貝,賦值操作,而只是為了防止內存洩漏的發生,scoped_ptr完全可以勝任。
更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2016-08/134315p2.htm