c++中std::auto_ptr的使用解析

前言

由於 C++ 語言沒有自動內存回收機制,程序員每次 new 出來的內存都要手動 delete。程序員忘記 delete,流程太複雜,最終導致沒有 delete,異常導致程序過早退出,沒有執行 delete 的情況並不罕見。

用智能指針便可以有效緩解這類問題,本文主要講解std::auto_ptr智能指針。

對於編譯器來說,智能指針實際上是一個棧對象,並非指針類型,在棧對象生命期即將結束時,智能指針通過析構函數釋放有它管理的堆內存。所有智能指針都重載了“operator->”操作符,直接返回對象的引用,用以操作對象。訪問智能指針原來的方法則使用“.”操作符。

訪問智能指針包含的裸指針則可以用 get() 函數。由於智能指針是一個對象,所以if (my_smart_object)永遠爲真,要判斷智能指針的裸指針是否爲空,需要這樣判斷:if (my_smart_object.get())。

智能指針包含了 reset() 方法,如果不傳遞參數(或者傳遞 NULL),則智能指針會釋放當前管理的內存。如果傳遞一個對象,則智能指針會釋放當前對象,來管理新傳入的對象。

測試1

#include <iostream>
#include <memory>

class Simple {

 public:

  Simple(int param = 0) {

    number = param;

    std::cout << "Simple: " << number << std::endl; 

  }


  ~Simple() {

    std::cout << "~Simple: " << number << std::endl;

  }

 
  void PrintSomething() {

    std::cout << "PrintSomething: " << info_extend.c_str() << std::endl;

  }


  std::string info_extend;

  int number;

};

void TestAutoPtr() {

std::auto_ptr<Simple> my_memory(new Simple(1));   // 創建對象,輸出:Simple:1

if (my_memory.get()) {                            // 判斷智能指針是否爲空

my_memory->PrintSomething();                    // 使用 operator-> 調用智能指針對象中的函數

my_memory.get()->info_extend = "Addition";      // 使用 get() 返回裸指針,然後給內部對象賦值

my_memory->PrintSomething();                    // 再次打印,表明上述賦值成功

(*my_memory).info_extend += " other";           // 使用 operator* 返回智能指針內部對象,然後用“.”調用智能指針對象中的函數

my_memory->PrintSomething();                    // 再次打印,表明上述賦值成功

  }

}




int main ()
{

	TestAutoPtr(); 
	return 0;
}

編譯執行,程序執行正常
在這裏插入圖片描述

測試2

std::auto_ptr 屬於 STL,當然在 namespace std 中,包含頭文件 #include 便可以使用。std::auto_ptr 能夠方便的管理單個堆內存對象。

#include <iostream>
#include <memory>

class Simple {

 public:

  Simple(int param = 0) {

    number = param;

    std::cout << "Simple: " << number << std::endl; 

  }

 

  ~Simple() {

    std::cout << "~Simple: " << number << std::endl;

  }

 

  void PrintSomething() {

    std::cout << "PrintSomething: " << info_extend.c_str() << std::endl;

  }

 

  std::string info_extend;

  int number;

};


/*
void TestAutoPtr() {

std::auto_ptr<Simple> my_memory(new Simple(1));   // 創建對象,輸出:Simple:1

if (my_memory.get()) {                            // 判斷智能指針是否爲空

my_memory->PrintSomething();                    // 使用 operator-> 調用智能指針對象中的函數

my_memory.get()->info_extend = "Addition";      // 使用 get() 返回裸指針,然後給內部對象賦值

my_memory->PrintSomething();                    // 再次打印,表明上述賦值成功

(*my_memory).info_extend += " other";           // 使用 operator* 返回智能指針內部對象,然後用“.”調用智能指針對象中的函數

my_memory->PrintSomething();                    // 再次打印,表明上述賦值成功

  }

}

*/

void TestAutoPtr2() {

  std::auto_ptr<Simple> my_memory(new Simple(1));

  if (my_memory.get()) {

    std::auto_ptr<Simple> my_memory2;   // 創建一個新的 my_memory2 對象

    my_memory2 = my_memory;             // 複製舊的 my_memory 給 my_memory2

    my_memory2->PrintSomething();       // 輸出信息,複製成功

    my_memory->PrintSomething();        // 崩潰

  }

}


int main ()
{


	TestAutoPtr2(); 
	return 0;
}

編譯,用GDB來調試查看。
std::auto_ptr my_memory(new Simple(1));執行時:
在這裏插入圖片描述
std::auto_ptr執行時解析:

/**
* 構造函數,將auto_ptr綁定到指針__p。
* __p是一個指向new出來的對象的指針,默認爲0(NULL)是說auto_ptr的構造函數可以不傳參構造,
* 這時成員_M_ptr=0,如果接着解引用auto_ptr對象,將Segmentation fault。當然,通常應用auto_ptr的構造
* 函數會傳參的。auto_ptr提供了get函數來判斷_M_ptr是否爲空、reset函數重置_M_ptr指針。
* 在繼承情況下,_M_ptr可以是__p的基類型。
* 構造函數聲明爲explicit表示禁止參數的自動類型轉換(因爲它們總是邪惡的)。
*
*/

      explicit auto_ptr(element_type* __p = 0) throw () :

            _M_ptr(__p) {

      }

my_memory.get();執行時:
在這裏插入圖片描述
get()函數調用解析:

/**
* 返回auto_ptr管理的指針,這通常用於判斷指針是否爲空的情況,所以,如果要判斷
* auto_ptr管理的指針是否爲空,不要使用if(auto_ptr_obj){}而是使用get函數(實際上,
* 因爲auto_ptr並沒用定義指向element_type的dumb指針的隱式類型轉換操作符,所以根本
* 編譯不過if(auto_ptr_obj))。
* 但是,auto_ptr並沒有禁止你進一步操作你得到的指針,甚至delete它使
* auto_ptr對象內置的指針懸空。
*/

      element_type* get() const throw () {

            return _M_ptr;

      }

my_memory2 = my_memory; 執行時:
在這裏插入圖片描述
可以看到執行 my_memory2 = my_memory這句話後,my_memory的值傳給my_memory2後,my_memory被清零,讓出了內存管理權,my_memory2 完全奪取了 my_memory 的內存管理所有權。
後續調用my_memory->PrintSomething(); 時進行空指針的引用,程序報錯了:Segmentation fault (core dumped)
在這裏插入圖片描述
最終如上代碼導致崩潰,如上代碼時絕對符合 C++ 編程思想的,居然崩潰了,跟進std::auto_ptr 的源碼後,我們看到,罪魁禍首是“my_memory2 = my_memory”,這行代碼,my_memory2 完全奪取了 my_memory 的內存管理所有權,導致 my_memory 懸空,最後使用時導致崩潰。

所以,使用 std::auto_ptr 時,絕對不能使用“operator=”操作符。作爲一個庫,不允許用戶使用,確沒有明確拒絕[1],多少會覺得有點出乎預料。

測試3

#include <iostream>
#include <memory>

class Simple {

 public:
  Simple(int param = 0) {
    number = param;
    std::cout << "Simple: " << number << std::endl; 
  }

  ~Simple() {
    std::cout << "~Simple: " << number << std::endl;
  }

  void PrintSomething() {
    std::cout << "PrintSomething: " << info_extend.c_str() << std::endl;
  }
  
  std::string info_extend;
  int number;

};


void TestAutoPtr3() {

  std::auto_ptr<Simple> my_memory(new Simple(1));

  if (my_memory.get()) {

    my_memory.release();

  }

}
int main ()
{
	TestAutoPtr3(); 
	return 0;
}

編譯調試
在這裏插入圖片描述
在這裏插入圖片描述
可以看到my_memory.release();這一句中執行時,先將指針置位0,然後delete 這個執行,實際創建的對象被沒有釋放掉,即對象沒有被析構,沒有輸出“~Simple: 1”。這就造成了內存的浪費。
雖然這次執行正常退出了,但多次執行,可能會把內存搞崩潰了。
在這裏插入圖片描述
如果程序沒有通過delete、free語句現實釋放內存的話,不論是崩潰(崩潰前爲執行釋放語句)還是正常退出(即沒有在程序中寫下釋放語句,但程序‘正常’退出)。那麼這沒有回收的內存將在你每一次關機時又系統回收。
這是一個致命的bug:加入程序持續運行多次,則可能在後面的某一此中因內存溢出而崩潰,而可能根本找不到原因(因爲前幾次都是正確的)。
當我們不想讓 my_memory 繼續生存下去,我們調用 release() 函數釋放內存,結果卻導致內存泄露(在內存受限系統中,如果my_memory佔用太多內存,我們會考慮在使用完成後,立刻歸還,而不是等到 my_memory 結束生命期後才歸還)。

測試4

#include <iostream>
#include <memory>

class Simple {

 public:

  Simple(int param = 0) {

    number = param;

    std::cout << "Simple: " << number << std::endl; 

  }

 

  ~Simple() {

    std::cout << "~Simple: " << number << std::endl;

  }

 

  void PrintSomething() {

    std::cout << "PrintSomething: " << info_extend.c_str() << std::endl;

  }

 

  std::string info_extend;

  int number;

};


void TestAutoPtr3() {

  std::auto_ptr<Simple> my_memory(new Simple(1));

  if (my_memory.get()) {

    Simple* temp_memory = my_memory.release();

    delete temp_memory;

  }

}

void TestAutoPtr4() {

  std::auto_ptr<Simple> my_memory(new Simple(1));

  if (my_memory.get()) {

    my_memory.reset();  // 釋放 my_memory 內部管理的內存

  }

}


int main ()
{
	TestAutoPtr3(); 
	TestAutoPtr4(); 	
	return 0;
}

在這裏插入圖片描述
delete temp_memory;執行時,調用了析構函數來釋放對象的內存空間。

在這裏插入圖片描述
my_memory.reset(); 執行時,調用了析構函數來釋放對象的內存空間。

總結

原來 std::auto_ptr 的 release() 函數只是讓出內存所有權,這顯然也不符合 C++ 編程思想。

總結:std::auto_ptr 可用來管理單個對象的對內存,但是,請注意如下幾點:

(1) 儘量不要使用“operator=”。如果使用了,請不要再使用先前對象。

(2) 記住 release() 函數不會釋放對象,僅僅歸還所有權。

(3) std::auto_ptr 最好不要當成參數傳遞(讀者可以自行寫代碼確定爲什麼不能)。

(4) 由於 std::auto_ptr 的“operator=”問題,有其管理的對象不能放入 std::vector等容器中。

(5) ……

使用一個 std::auto_ptr 的限制還真多,還不能用來管理堆內存數組,這應該是你目前在想的事情吧,我也覺得限制挺多的,哪天一個不小心,就導致問題了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章