標籤:c++ 智能指標 記憶體流失
概述:C++中有很多種智能指標,auto_ptr就是其中的一種,該智能指標主要是為瞭解決“因程式異常退出發生的記憶體流失”這類問題的。
我們先來看下面的問題代碼
#include<iostream>#include<memory>#include<exception>using namespace std;//一般指標的處理方式template<typename T>class OldClass{public:OldClass(T a){ptr = new T(a);}~OldClass(){delete ptr;}private:T * ptr;};int main(){OldClass<int> a(2);int * p = new int(1);throw runtime_error("1");//程式發生異常退出delete p;}在上面的例子中類OldClass中在建構函式中為成員指標開闢記憶體資源,在解構函式中釋放記憶體資源;現在如果在main主函數中發生異常即throw runtime_error("1");導致程式退出;則該程式將會造成嚴重影響,即:一、main函數中的指標p未被釋放;二、由於程式的異常結束將類對象a的解構函式也將不會被調用,即對象a自身的記憶體資源也沒有被釋放。這兩種情況都將會造成記憶體流失。
常規的解決辦法就是利用異常處理來解決,具體修改代碼如下:
int main(){OldClass<int> a(2);try{int * p = new int(1);//發生異常退出try{throw runtime_error("1");}catch(exception ex){delete p;//必須針對每一個可能因異常而未釋放資源的指標先進行釋放處理throw ex;//再繼續將原異常拋出}delete p;}catch(runtime_error ex){cerr<<ex.what()<<endl;//此處進行異常處理}}即必須針對每一個可能因為異常而無法釋放的指標在異常傳遞的中間先釋放該指標後再繼續原異常拋出,可見這種辦法很為複雜,因為必須為每一個指標都進行類似的處理。
智能指標的出現:為了方便解決該問題的首段之一就是智能指標,即可以將任何一種類型(包括自訂類型)轉換成自能指標,轉換成智能指標後由於智能指標本身就是地區變數,所以無論程式是正常退出還是異常退出,只要程式退出時則智能指標都會被自動銷毀釋放記憶體資源。
下面對智能指標的常見用法和注意點做幾點詳細介紹:
一、智能指標的初始化時,只能用一個智能指標給另一個智能指標初始化,普通指標則不能。
<span style="white-space:pre"></span>auto_ptr<int> a = auto_ptr<int>(new int(2));int *p = new int(1);//auto_ptr<int> b = p;//報錯auto_ptr<int> c(p);cout<<*a<<endl;cout<<*c<<endl;/*輸出結果:2 <span style="white-space:pre"></span>1 <span style="white-space:pre"></span>請按任意鍵繼續. . .*/
二、每一個智能指標都是它所指向的對象的擁有者,且每一個對象都只能有一個擁有者,所以智能指標的建構函式和賦值操作原理上都是擁有權ownership的轉移
auto_ptr<int> a = auto_ptr<int>(new int(2));auto_ptr<int> c = auto_ptr<int>(new int(1));//a將值為2的對象的擁有權轉移給c,a變成一個null指標//c將值為1的對象銷毀並交出擁有權後接受a轉移的擁有權c = a;//c將擁有權轉移給d,自己變成null指標auto_ptr<int> d(c);
三、智能指標和一般指標的用法一致,都含有operate*和operate->操作,但是智能指標卻不能進行任何算術運算,這是由於智能指標擁有權的特性決定的。
四、智能指標做為函數的參數類型或返回值類型時都會發生對象擁有權的轉移,如果將函數外部對象的擁有權轉移進入函數內,而不再用返回值將其擁有權轉移出來的話,則該外部對象將會隨著函數調用的結束會被銷毀,這是很可怕的事情所以要慎用。
五、在函數間傳遞智能指標時最安全的方式是用常量引用constant reference,這樣外部對象的擁有權不會被轉移到函數內部的局部變數中,當然這樣的對象轉遞進去後是不能修改的,不能賦值給別的對象或被別的對象給賦值;也你建議用引用reference去傳遞智能指標對象,因為這樣很容易造成擁有權的混淆。
auto_ptr<int> a = auto_ptr<int>(new int(2));auto_ptr<int> c = auto_ptr<int>(new int(1));const auto_ptr<int> & d = c;cout<<*a<<endl;cout<<*c<<endl;cout<<*d<<endl;//輸出2、1、1//a = d;編譯報錯
值得加以說明的是:對於auto_ptr對象的constant reference與一般的對象的常量引用有所不同,對於auto_ptr智能指標對象const並非意味著你不能更改該指標所擁有的對象,而是意味著你不能更改auto_ptr的擁有權。
六、不村子針對數組array的智能指標,因為auto_ptr是透過delete操作而非delete[]操作來銷毀對象的。
七、智能指標對象不能做為標準容器的元素,因為標準容器中最基本的複製和拷貝操作都會使原有的auto_ptr對象交出擁有權,而不是拷貝給新的對象。
下面附上智能指標運用完整例子:
#include<iostream>#include<memory>#include<exception>using namespace std;//智能指標處理template<typename T>class NewClass{public:NewClass(T a){ptr = auto_ptr<T>(new T(a));}NewClass(const NewClass<T> &nc):ptr(new T(nc.Get())){}~NewClass(){}T Get(){return *ptr;}void Set(T t){*ptr = t;}private:auto_ptr<T> ptr;};template<typename T>void fun(const auto_ptr<T> & ap){//該函數的功能是將指標所指對象中的資料成員加陪ap->Set((*ap).Get()*2);}int main(){//建立NewClass對象的智能指標auto_ptr<NewClass<int>> a(new NewClass<int>(2));cout<<a->Get()<<endl;//將a中的資料加倍fun(a);cout<<a->Get()<<endl;/*24請按任意鍵繼續. . .*/}
最後簡單小結一下:其實aoto_ptr智能指標確實還是挺方便的,只是自己在運用的時候多注意對象擁有者的唯一性,不要輕易的進行指標間的賦值或傳值就可以了。