1. 程式人生 > >C++智慧指標之auto_ptr

C++智慧指標之auto_ptr

標頭檔案<memory>

總結為一句話:auto_ptr是獨佔指標,它的出現是能夠自動析構動態分配的記憶體,避免記憶體洩漏,但是auto_ptr有很多弊端,下面會通過示例和講解一一將弊端和用法展現出來。


  1. auto_ptr不能初始化為指向非動態記憶體(原因很簡單,delete 表示式會被應用在不是動態分配的指標上這將導致未定義的程式行為)。物件通過初始化只能指向由new建立的動態記憶體,它是這塊記憶體的擁有者。

構造auto_ptr物件示例程式碼

// 直接構造智慧指標
auto_ptr<int> p(new int(1));//推薦
// 將已存在的指向動態記憶體的普通指標作為引數來構造 int* np = new int(1); auto_ptr<int> p(np);
  1. auto_ptr的用途:管理物件的生命週期,不造成記憶體洩漏。即:當auto_ptr物件生命週期結束時,其解構函式會將auto_ptr物件擁有的動態記憶體自動釋放。即使發生異常,通過異常的棧展開過程也能將動態記憶體釋放。
  2. auto_ptr不能用來管理陣列指標:auto_ptr的內部實現中,解構函式中刪除物件使用delete而不是delete[]。
    程式碼解釋:使用auto_ptr的方式,在ap析構時,執行delete,僅僅釋放了陣列的第一個元素的空間,仍然會造成記憶體洩漏,所有使用auto_ptr管理陣列不合理的。
int *p = new int[100];
auto_ptr<int> ap(p);
  1. 由於auto_ptr物件析構時會刪除它所擁有的指標,所以使用時避免多個auto_ptr物件管理同一個指標。程式設計師在寫程式碼時,很容易將智慧指標指向同一塊記憶體,並且該問題還不好察覺,因此引出下文的week_ptr禁止同一個week_ptr指標指向同一塊記憶體(即:兩個week_ptr指標指向同一塊記憶體時,編譯報錯)

程式碼解釋:這樣使用會造成p1和p2在析構時都試圖刪除np,C++標準中多次刪除同一個物件會導致未定義的行為。並且當p1析構而p2仍然被使用時,會導致空指標訪問風險。

int* np = new int(1);
auto_ptr<int> p1(np);
auto_ptr<int> p2(np);
  1. C++中對一個空指標NULL執行delete操作是安全的。所以在auto_ptr的解構函式中無須判斷它所擁有指標是否為空。
  2. auto_ptr智慧指標的拷貝、賦值,會發生所有權的轉移,詳解見下:因為一塊動態記憶體智慧由一個智慧指標獨享,所以在拷貝構造或賦值時都會發生擁有權轉移的過程。p1將失去對字串記憶體的所有權,而p2將其獲得。物件銷燬時,p2負責記憶體的自動銷燬

程式碼解釋:p1失去所有權變為空,p2獲得p1的所有權

auto_ptr< string > p1( new string( "Brontosaurus" ) );
auto_ptr< string > p2(p1);  //利用p1來構造p2,發生拷貝構造
auto_ptr< int > p1( new int( 1024 ) );
auto_ptr< int > p2( new int( 2048 ) );
p1 = p2;  //賦值運算
  1. 不要將auto_ptr作為函式引數按值傳遞。因為在函式呼叫過程中在函式的作用域中會產生一個區域性的臨時auto_ptr物件來接收傳入的 auto_ptr(拷貝構造),這樣,傳入的實參auto_ptr的對其指標的所有權轉移到了臨時auto_ptr物件上,臨時auto_ptr在函式退出時析構,所以當函式呼叫結束,原實參所指向的物件已經被刪除了。
void func(auto_ptr<int> ap)
{
cout << *ap << endl;
}

auto_ptr<int> ap(new int(1));
func(ap); //值傳遞後,ap的所有權轉移,ap失效,將不再擁有物件
cout << *ap << endl;//錯誤,函式呼叫結束後,ap已經不再擁有任何物件了
  1. 非要將auto_ptr作為引數傳遞時,一定要使用const &型別。
    解釋:按引用傳遞在呼叫函式是不會發生所有權轉移,但是無法預測函式體內的操作,有可能在函式體內進行了所有權的轉移,因此按引用傳遞auto_ptr作為函式引數也是不安全的。使用const 引用傳遞則可以阻止在函式體內對auto_ptr物件的所有權轉移。如果不得不使用auto_ptr物件作為函式引數時,儘量使用const引用傳遞引數。
  2. auto_ptr支援所擁有的指標型別之間的隱式型別轉換。
    程式碼解釋:下列程式碼就可以通過,實現從auto_ptr到auto_ptr的隱式轉換,因為derived*可以轉換成base*型別
class base{};
class derived: public base{};

auto_ptr<base> apbase = auto_ptr<derived>(new derived);
  1. 重要:auto_ptr物件不能作為STL容器元素

auto_ptr常用的成員函式

1、 get()
返回auto_ptr指向的那個物件的記憶體地址。如下例:

int* p = new int(33);
cout << "the adress of p:"<< p<< endl;    // 00481E00
auto_ptr<int> ap1(p);
cout << "the adress of ap1: "<< &ap1<< endl; //0012FF68
cout << "the adress of the objectwhich ap1 point to: " << ap1.get()<< endl; // 00481E00

輸出結果分析:第一行與第三行相同,都是new int(33)所在的那塊記憶體的地址。第二行是ap1是這個類物件本身所在記憶體的地址&ap1。

2、reset()
重新設定auto_ptr指向的物件。類似於賦值操作,但賦值操作不允許將一個普通指標指直接賦給auto_ptr,而reset()允許。

程式碼解釋:剝奪pstr_auto擁有"Brontosaurus"字元記憶體的所有權,這塊記憶體首先會被釋放,之後pstr_auto再擁有"Long-neck"字元記憶體的所有權。

auto_ptr< string > pstr_auto( new string( "Brontosaurus" ) );
pstr_auto.reset( new string( "Long-neck" ) ); //reset

pstr_auto.reset(0); //可以釋放pstr_auto物件,銷燬記憶體。

3、release()
功能:p1.release();表示返回p1指向的那個物件的記憶體地址,並釋放對p1對該這塊記憶體的所有權。 ⇒ ⇒ ⇒ 用此函式初始化auto_ptr時可以避免兩個auto_ptr物件擁有同一個物件的情況(與get函式相比)。

例子如下:

auto_ptr< string > pstr_auto( newstring( "Brontosaurus" ) );

//pstr_auto、pstr_auto2都具有所有權,要避免這種情況
auto_ptr< string > pstr_auto2(pstr_auto.get() ); 

//release可以首先釋放對物件的所有權,再返回該物件,此時只有pstr_auto2具有所有權
auto_ptr< string > pstr_auto2(pstr_auto.release() );