C++動態內存管理和智能指針:malloc和new,free和delete,在C語言中,我們通常用malloc和free來動態的管理內存,其中malloc用來在堆上開辟空間,而free用來釋放malloc或其他在堆上動態開辟內存的函數所開辟的空間。在C++中,我們用new/delete;new[]/delete[]來動態的管理內存,相比於C語言中的malloc和free,他們之間有什麼差別呢?
首先,它們的調用方式不相同,其中malloc和free采用下面的方式來調用
int* p=(int*)malloc(sizeof(int)*4); //使用malloc函數開辟4個int類型的空間 free(p); p=NULL; //釋放上面malloc所開辟的空間
在C++中,new和new[]/delete[]和delete主要采用下面的調用方式:
int* p=new int(4); //開辟一個int類型的對象,初值為4 delete p; //釋放上面所開辟的空間 int* p1=new int[4]; //開辟4個int類型的空間,無初值 delete[] p1; //釋放上面所開辟的空間
另外,它們之間更重要的差別是,new和delete,new[]和delete[]才開辟空間後和釋放空間前還分別調用了所開辟類型的構造函數以及析構函數。如下圖所示:
另外,C中的malloc開辟空間時如果出現了內存耗盡的情況的話會直接返回NULL,而C++中的new會拋出異常,不過這裡我們也可以聲明其不拋出異常使其在內存耗盡的情況下返回NULL.
int* p=new int[100000000000]; //出現內存耗盡,拋出異常的方式 int* p1=new(nothrow) int[10000000]; //不拋出異常,出現內存耗盡返回NULL
在平時寫代碼時,我們用new開辟內存空間時,即使我們記住了需要配對使用,但難免有時候會出現內存洩漏的情況;例如:
{ int* p=new int(5); ... ... thorw ...; ` ... delete p; }
在上面的這段代碼中,即使我們在最後對new分配的空間進行了delete,如果中間因為某些原因我們拋出了一個異常,並且我們在異常處理函數中沒有對這塊內存進行處理,那麼這塊內存便會一直停留在操作系統中,從而導致了內存洩漏。
對於這種情況,我們因此引入了智能指針,智能指針采用RAII(資源分配即初始化機制)的內存管理方式,即智能指針不是常規指針,只是他的行為類似指針,其實是一種管理內存的對象,在其構造時獲取資源,在其對象生命控制期對其訪問使用有效,最後在其析構期釋放內存。對此不管是否拋出異常,只要離開了當前的作用域,作用域中的智能指針便會自動調用delete或delete[]進行釋放資源。
在boost庫中主要有下面幾種智能指針:
auto_ptr采用可以采用copy語義來轉移指針資源的所有權的同時將原指針置為NULL,這跟通常理解的copy行為是不一致的,而這樣的行為要有些場合下不是我們希望看到的,例如sort的快排實現中有將元素復制到某個局部臨時對象中,但對於auto_ptr,卻將原元素置為null,這就導致最後的排序結果中可能有大量的null從而導致異想不到的後果。
scoped_ptr和auto_ptr都表示唯一的所有權持有者,區別在於scoped_ptr不允許拷貝構造和賦值的發生,它這這些函數定義為私有成員函數,從而使外部對象無法進行訪問。
下面我們來實現一個簡單的scoped_ptr
templateclass Scoped_ptr { public: explicit Scoped_ptr(T* ptr=NULL) :_ptr(ptr) {} T& operator*() { return *_ptr; } T* operator->() { //return &(operatpr*()); //寫法1 return _ptr; } ~Scoped_ptr() { delete _ptr; } private: Scoped_ptr(const Scoped_ptr& sp) {} operator=(const Scoped_ptr& sp) {} protected: T* _ptr; };
其中,編譯器對->運算符的重載進行了優化
例如:
struct A { int _a; int _b; int _c; }; Scoped_ptr pa(new A()); pa->_a; //(pa->_a):pa.operator->->_a; 兩步變一步
shared_ptr采用了引用計數的方式來管理內存,如下圖:
使用庫中的shared_ptr指針:
* shared_ptr不允許隱式的強制類型轉換(構造函數和拷貝構造函數前面聲明了explicit)
例如:不能這麼使用shared_ptr
shared_ptr返回值必須要顯示的綁定到shared_ptr的指針上pa=new int(1); //正確使用方式如下 shared_ptr pa(new int(1));
shared_ptrp.reset() //若p的shared_ptr的引用計數為1,reset會釋放p所指向的對象 make_shared //創建一個shared_ptr的對象 使用shared_ptr的誤區:使用內置指針來訪問shared_ptr和使用shared_ptr來管理內置指針 智能指針類型定義了一個名為get的函數,它返回一個內置指針,指向智能指針管理的對象(注意在使用get時不能delete指針,否則後面可能會發生對空指針的解引用的行為)ReturnPtr() { //正確方式 shared_ptr tmp(new int(1)); return tmp; //錯誤方式 return new int(1); }
此時weak_ptr就出現了,它采用弱引用的方式,彌補了shared_ptr循環引用的問題它的構造和析構不會引起引用記數的增加或減少。沒有重載*和->但可以使用lock獲得一個可用的shared_ptr對象。
weak_ptr:
namespace boost { templateclass weak_ptr { public: template weak_ptr(const shared_ptr & r); weak_ptr(const weak_ptr& r); ~weak_ptr(); T* get() const; bool expired() const; shared_ptr lock() const; }; }
總結::智能指針能解決C++中的內存洩漏問題,我們應在程序中多去使用智能指針,另外還要注意shared_ptr引起的循環引用的問題。