管理指標成員

來源:互聯網
上載者:User

為什麼當類的成員有指標時,需要特別注意?
因為當一個指標複製給另一個指標時,兩個指標指向同一個對象,使用二者之一修改指向的對象,更特殊的,如果使用一個指標刪除了對象時,另一個指標還以為對象存在,這就會造成很大的災難。
通常有3種方法管理指標成員:
1.常規管理。就是將一個指標複製給另一個指標,這樣做的好處是不需要額外定義複製控制,但是會出現前面所提到的問題。
2.智能指標。雖然這些指標共用同一個資料,利用一個資料結構記錄了有多少指標指向這個資料。當只剩下一個指標指向這個資料時,才能進行刪除這個資料的操作,這樣可以避免懸垂指標。
3.定義值型類。此時指標並不直接指向對象,而是建立一個對象的副本,指標唯一的指向這個副本。這樣這些指標就不再“耦合”,通過指標對對象的修改、刪除只能影響各自的副本。

下面分別介紹這3種情況,先看第一種情況。
通過一個類來說明這種直接複製的特點:

class HasPtr{public://建構函式HasPtr(int *p,int i):ptr(p),val(i){}//get函數int *get_ptr()const{return ptr;}int get_int() const{std::cout<<"val = "<<val<<std::endl;return val;}//set函數void set_ptr(int *p){ptr = p;}void set_int(int i){val = i;}//get和set指標指向的對象int get_ptr_val()const{std::cout<<"ptr point to "<<*ptr<<std::endl;return *ptr;}void set_ptr_val(int val)const {*ptr = val;}private:int *ptr;int val;};

這個類有兩個資料成員,一個int的值,一個int*的指標。它倆之間並沒有什麼關係(並不是我們美好想象的那樣:指標指向這個值)。

然後定義了一些簡單的set、get函數。

通過下面的程式就能看出它們的關係了:

int main(){int a = 0;int *p = &a;HasPtr ptr1(p,a);cout<<"value = "<<ptr1.get_int()<<endl;cout<<"point is point to "<<ptr1.get_ptr_val()<<endl;//資料和指標不相關ptr1.set_int(1);cout<<"value = "<<ptr1.get_int()<<endl;cout<<"point is point to "<<ptr1.get_ptr_val()<<endl;//用ptr1初始化ptr2HasPtr ptr2(ptr1);//值已被修改為1cout<<"value = "<<ptr2.get_int()<<endl;//指標未被修改,依然指向0cout<<"point is point to "<<ptr2.get_ptr_val()<<endl;a = 10;cout<<"value = "<<ptr2.get_int()<<endl;//指標指向的內容發生變化cout<<"point is point to "<<ptr2.get_ptr_val()<<endl;int *ip = new int(42);//動態分配值HasPtr ptr3(ip,42);HasPtr ptr4(ptr3);delete ip;//刪除指標ptr3.set_ptr_val(0);//發生災難return 0;}

那就是它們的資料時無關的,而指標卻是指向同一個對象的,修改一個指標指向的內容會影響另一個。

 

接下來我們看看智能指標。
智能指標希望達到的效果是:複製行為跟前面的類似:它的複製對象時,副本和來源物件的指標指向相同。改變其中的一個會是另一個也改變。但是在在構函數中刪除指標時,不會無條件的刪除指標導致出現前面的null 指標情況,而是判斷是否有其他指標也指向同一個基礎對象,如果有的話,就不能刪除它,直到沒有其他對象的指標指向這個基礎對象了,才能刪除它。
如何?這個功能呢?說起來很簡單,設定一個“引用計數”(或者叫“使用計數”)。有多少指標共用一個對象,就記錄為幾,只有當所有的指標都被刪除了,才會刪除對象。
具體的說,當建立一個新對象時,初始化指標並使引用計數為1。當這個對象作為另一個對象的複本建立時,引用計數加1;如果是賦值操作時,左運算元的引用計數減少1,而右操作的引用計數增加1。調用解構函式時,會減少引用計數的值,直到引用計數減為0,才會刪除基礎對象。

說起來容易做起來難,首先要解決的問題就是引用計數放到哪裡。其中的一種解決方案是單獨定義一個引用計數類來封裝引用計數以及相關的指標。我們先看程式:

//類的聲明class HasPtr;class U_Ptr{//聲明友元類:HasPtr可以訪問本類的私人函數friend class HasPtr;//原來HasPtr類的資料成員:指標,變成了U_Ptr類的成員int *ip;//引用計數size_t use;//建構函式U_Ptr(int *p):ip(p),use(1){}//解構函式:刪除指標~U_Ptr(){delete ip;}};

注意,這個類的所有成員都是私人的,普通使用者並不能訪問。我們將這個類設為HasPtr的友元,這樣HasPtr就能訪問這個類的成員。這個類定義了指標ip取代了原來HasPtr中的指標,也定義了引用計數。它的建構函式接受一個指向int的指標,用它來初始化自己的指標,而引用計數初始化為1。解構函式刪除這個指標。

而原來的HasPtr類定義如下:

class HasPtr{public://建構函式:HasPtr(int *p,int i):ptr(new U_Ptr(p)),val(i){}//複製建構函式HasPtr(const HasPtr &orig):ptr(orig.ptr),val(orig.val){++ptr->use;}//賦值操作HasPtr& operator=(const HasPtr&);//解構函式~HasPtr(){if(--ptr->use == 0)delete ptr;}//set和get函數//裡面與指標有關的多變成訪問ptr裡面的ip指標int *get_ptr()const{return ptr->ip;}int get_int()const {return val;}void set_ptr(int *p){ptr->ip = p;}void set_int(int i){val = i;}int get_ptr_val()const {return *ptr->ip;}void set_ptr_val(int i){*ptr->ip = i;}private://引用計數類對象U_Ptr *ptr;//值int val;};HasPtr& HasPtr::operator=(const HasPtr &rhs){++rhs.ptr->use;if(--ptr->use == 0)delete ptr;ptr = rhs.ptr;val = rhs.val;return *this;}

私人成員的int型指標變成了指向U_Ptr 的類型。建構函式的輸入是一個int值來初始化自己的值,在初始化自己的U_Ptr指標時,動態分配了一個U_Ptr類,調用這個類的建構函式來初始化它。複製建構函式中,值和指標的複製都是直接的,但是複製以後引用計數會加1。賦值建構函式有點複雜,它是得賦值的有運算元(rhs)的引用計數加1,左運算元的引用計數減1(如果減到了0,刪除左運算元的指標)。然後用右運算元的指標和值複製給左運算元的指標和值。解構函式要判斷指向某個數的指標是否為0,如果是,則刪除這個指標。而在set和get函數中,只用把原來直接擷取指標變為獲得U_Ptr類中的指標就行了。

 

最後我們看看定義值型類。
首先這個名字就很奇怪。需要仔細解釋一下:“值型類”意味著定義的類跟值很像。而我們知道,當複製值時,複製的不是值本身,而是值的一個副本。我們希望建立的類也有這樣的特徵,這樣的話,雖然指標不能共用資料,但是它從根本上避免了指標的糾纏。先看一個例子:

class HasPtr{public://建構函式HasPtr(const int &p,int i):ptr(new int(p)),val(i) {}//複製建構函式HasPtr(const HasPtr &orig):ptr(new int (*orig.ptr)),val(orig.val){}//複製操作符HasPtr& operator=(const HasPtr&);//解構函式~HasPtr(){delete ptr;}int get_ptr_vaule()const {return *ptr;}int get_int()const{return val;}void set_ptr(int *p){ptr = p;}void set_ptr_val(int i){val = i;}int *get_ptr()const {return ptr;}void set_ptr_val(int p)const{*ptr = p;}private:int *ptr;int val;};HasPtr& HasPtr::operator=(const HasPtr &rhs){*ptr = *rhs.ptr;val = rhs.val;return *this;}

可以發現,對於建構函式和複製構於造函數,對於實參指標,採取的辦法都是建立一個一個新的指標,讓新指標的指向實參相同,然後用它來初始化資料成員中的指標。在賦值操作符中,我們並沒有將某個地址給指標,而是讓指標指向一個具體的值,這個值是賦值操作符的有運算元的指標指向的。這意味著進行複製操作以後,兩個運算元的指標成員指向的值相同(而不是指向同一個對象!)最後,在解構函式中,需要刪除這個指標

 

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.