管理类的指针成员
設(shè)計(jì)具有指針成員的類(lèi)時(shí),類(lèi)設(shè)計(jì)者必須首先需要決定的是該指針應(yīng)提供什么行為。
將一個(gè)指針復(fù)制到另一個(gè)指針時(shí),兩個(gè)指針指向同一對(duì)象。
當(dāng)兩個(gè)指針指向同一對(duì)象時(shí),可能使用任一指針改變基礎(chǔ)對(duì)象。
類(lèi)似地,很可能一個(gè)指針刪除了一對(duì)象時(shí),另一指針的用戶還認(rèn)為基礎(chǔ)對(duì)象仍然存在。
指針成員默認(rèn)具有與指針對(duì)象同樣的行為。
然而,通過(guò)不同的復(fù)制控制策略,可以為指針成員實(shí)現(xiàn)不同的行為。
大多數(shù) C++ 類(lèi)采用以下三種方法之一管理指針成員:
1. 指針成員采取常規(guī)指針型行為。這樣的類(lèi)具有指針的所有缺陷但無(wú)需特殊的復(fù)制控制。
2. 類(lèi)可以實(shí)現(xiàn)所謂的“智能指針”行為。指針?biāo)赶虻膶?duì)象是共享的,但類(lèi)能夠防止懸垂指針。
3. 類(lèi)采取值型行為。指針?biāo)赶虻膶?duì)象是唯一的,由每個(gè)類(lèi)對(duì)象獨(dú)立管理。
[1. 一個(gè)帶指針成員的簡(jiǎn)單類(lèi)]
為了闡明所涉及的問(wèn)題,我們將實(shí)現(xiàn)一個(gè)簡(jiǎn)單類(lèi),該類(lèi)包含一個(gè) int 值和一個(gè)指針:
// class that has a pointer member that behaves like a plain pointerclass HasPtr {public:// copy of the values we're givenHasPtr(int *p, int i): ptr(p), val(i) { }// const members to return the value of the indicated data memberint *get_ptr() const { return ptr; }int get_int() const { return val; }// non const members to change the indicated data membervoid set_ptr(int *p) { ptr = p; }void set_int(int i) { val = i; }// return or change the value pointed to, so ok for const objectsint get_ptr_val() const { return *ptr; }void set_ptr_val(int val) const { *ptr = val; }private:int *ptr;int val;};1.1 默認(rèn)復(fù)制/賦值與指針成員
因?yàn)?HasPtr 類(lèi)沒(méi)有定義復(fù)制構(gòu)造函數(shù),所以復(fù)制一個(gè) HasPtr 對(duì)象將復(fù)制兩個(gè)成員:
int obj = 0;HasPtr ptr1(&obj, 42); // int* member points to obj, val is 42HasPtr ptr2(ptr1); // int* member points to obj, val is 42復(fù)制之后,ptr1 和 ptr2 中的指針指向同一對(duì)象且兩個(gè)對(duì)象中的 int 值相同。
但是,因?yàn)橹羔樀闹挡煌谒笇?duì)象的值,這兩個(gè)成員的行為看來(lái)非常不同。
復(fù)制之后,int 值是清楚和獨(dú)立的,而指針則糾纏在一起。
注意:具有指針成員且使用默認(rèn)合成復(fù)制構(gòu)造函數(shù)的類(lèi)具有普通指針的所有缺陷。
?????????尤其是,類(lèi)本身無(wú)法避免懸垂指針。
1.2 指針共享同一對(duì)象
復(fù)制一個(gè)算術(shù)值時(shí),副本獨(dú)立于原版,可以改變一個(gè)副本而不改變另一個(gè):
ptr1.set_int(0); // changes val member only in ptr1ptr2.get_int(); // returns 42ptr1.get_int(); // returns 0復(fù)制指針時(shí),地址值是可區(qū)分的,但指針指向同一基礎(chǔ)對(duì)象。
如果在任一對(duì)象上調(diào)用 set_ptr_val,則二者的基礎(chǔ)對(duì)象都會(huì)改變:
兩個(gè)指針指向同一對(duì)象時(shí),其中任意一個(gè)都可以改變共享對(duì)象的值。
1.3 懸垂指針(Dangling Pointers)
因?yàn)轭?lèi)直接復(fù)制指針,會(huì)使用戶面臨潛在的問(wèn)題:HasPtr 保存著給定指針。
用戶必須保證只要 HasPtr 對(duì)象存在,該指針指向的對(duì)象就存在:
這里的問(wèn)題是 ip 和 ptr 中的指針指向同一對(duì)象。
刪除了該對(duì)象時(shí),ptr 中的指針不再指向有效對(duì)象。
然而,ptr 卻沒(méi)有辦法得知對(duì)象已經(jīng)不存在了。
[2. 定義智能指針類(lèi)]
智能指針除了增加功能外,其行為像普通指針一樣。
本例中讓智能指針負(fù)責(zé)刪除共享對(duì)象。
用戶將動(dòng)態(tài)分配一個(gè)對(duì)象并將該對(duì)象的地址傳給新的 HasPtr 類(lèi)。
用戶仍然可以通過(guò)普通指針訪問(wèn)對(duì)象,但絕不能刪除指針。?
HasPtr 類(lèi)將保證在撤銷(xiāo)指向?qū)ο蟮淖詈笠粋€(gè) HasPtr 對(duì)象時(shí)刪除對(duì)象。
具體而言,復(fù)制對(duì)象時(shí),副本和原對(duì)象將指向同一基礎(chǔ)對(duì)象,
如果通過(guò)一個(gè)副本改變基礎(chǔ)對(duì)象,則通過(guò)另一對(duì)象訪問(wèn)的值也會(huì)改變。?
新的 HasPtr 類(lèi)需要一個(gè)析構(gòu)函數(shù)來(lái)刪除指針,但是,析構(gòu)函數(shù)不能無(wú)條件地刪除指針。
如果兩個(gè) HasPtr 對(duì)象指向同一基礎(chǔ)對(duì)象,
那么在兩個(gè)對(duì)象都撤銷(xiāo)之前,我們并不希望刪除基礎(chǔ)對(duì)象。
為了編寫(xiě)析構(gòu)函數(shù),需要知道這個(gè) HasPtr 對(duì)象是否為指向給定對(duì)象的最后一個(gè)。
2.1 引入使用計(jì)數(shù)
定義智能指針的通用技術(shù)是采用一個(gè)使用計(jì)數(shù)(或引用計(jì)數(shù))。
智能指針類(lèi)將一個(gè)計(jì)數(shù)器與類(lèi)指向的對(duì)象相關(guān)聯(lián)。
使用計(jì)數(shù)跟蹤該類(lèi)有多少個(gè)對(duì)象共享同一指針。
使用計(jì)數(shù)為 0 時(shí),刪除對(duì)象。每次創(chuàng)建類(lèi)的新對(duì)象時(shí),初始化指針并將使用計(jì)數(shù)置為 1。
當(dāng)對(duì)象作為另一對(duì)象的副本而創(chuàng)建時(shí),復(fù)制構(gòu)造函數(shù)復(fù)制指針并增加與之相應(yīng)的使用計(jì)數(shù)的值。
對(duì)一個(gè)對(duì)象進(jìn)行賦值時(shí),
賦值操作符減少左操作數(shù)所指對(duì)象的使用計(jì)數(shù)的值(如果使用計(jì)數(shù)減至 0,則刪除對(duì)象),
并增加右操作數(shù)所指對(duì)象的使用計(jì)數(shù)的值。
最后,調(diào)用析構(gòu)函數(shù)時(shí),析構(gòu)函數(shù)減少使用計(jì)數(shù)的值,如果計(jì)數(shù)減至 0,則刪除基礎(chǔ)對(duì)象。
唯一的創(chuàng)新在于決定將使用計(jì)數(shù)放在哪里。
計(jì)數(shù)器不能直接放在 HasPtr 對(duì)象中,為什么呢?考慮下面的情況:
如果使用計(jì)數(shù)保存在 HasPtr 對(duì)象中,創(chuàng)建 p3 時(shí)怎樣更新它?
可以在 p1 中將計(jì)數(shù)增量并復(fù)制到 p3,但怎樣更新 p2 中的計(jì)數(shù)?
2.2 使用計(jì)數(shù)類(lèi)
實(shí)現(xiàn)使用計(jì)數(shù)有兩種經(jīng)典策略,另一種方法將在后續(xù)章節(jié)中講述。
這里所用的方法中,需要定義一個(gè)單獨(dú)的具體類(lèi)用以封閉使用計(jì)數(shù)和相關(guān)指針:
這個(gè)類(lèi)的所有成員均為 private。
我們不希望用戶使用 U_Ptr 類(lèi),所以它沒(méi)有任何 public 成員。
將 HasPtr 類(lèi)設(shè)置為友元,使其成員可以訪問(wèn) U_Ptr 的成員。
U_Ptr 類(lèi)保存指針和使用計(jì)數(shù),每個(gè) HasPtr 對(duì)象將指向一個(gè) U_Ptr 對(duì)象,
使用計(jì)數(shù)將跟蹤指向每個(gè) U_Ptr 對(duì)象的 HasPtr 對(duì)象的數(shù)目。
U_Ptr 定義的構(gòu)造函數(shù)復(fù)制指針,而析構(gòu)函數(shù)刪除它。
構(gòu)造函數(shù)還將使用計(jì)數(shù)置為 1,表示一個(gè) HasPtr 對(duì)象指向這個(gè) U_Ptr 對(duì)象。
假定剛從指向 int 值 42 的指針創(chuàng)建一個(gè) HasPtr 對(duì)象,可以畫(huà)出這些對(duì)象,如下圖:
如果復(fù)制這個(gè)對(duì)象,則對(duì)象如下圖所示。
2.3 使用計(jì)數(shù)類(lèi)的使用
新的 HasPtr 類(lèi)保存一個(gè)指向 U_Ptr 對(duì)象的指針,U_Ptr 對(duì)象指向?qū)嶋H的 int 基礎(chǔ)對(duì)象。
必須改變每個(gè)成員以說(shuō)明的 HasPtr 類(lèi)指向一個(gè) U_Ptr 對(duì)象而不是一個(gè) int* 值。
先看看構(gòu)造函數(shù)和復(fù)制控制成員:
private:U_Ptr *ptr; // points to use-counted U_Ptr classint val;};
接受一個(gè)指針和一個(gè) int 值的 HasPtr 構(gòu)造函數(shù)使用其指針形參創(chuàng)建一個(gè)新的 U_Ptr 對(duì)象。
HasPtr 構(gòu)造函數(shù)執(zhí)行完畢后,HasPtr 對(duì)象指向一個(gè)新分配的 U_Ptr 對(duì)象,該 U_Ptr 對(duì)象存儲(chǔ)給定指針。
新 U_Ptr 中的使用計(jì)數(shù)為 1,表示只有一個(gè) HasPtr 對(duì)象指向它。
復(fù)制構(gòu)造函數(shù)從形參復(fù)制成員并增加使用計(jì)數(shù)的值。
復(fù)制構(gòu)造函數(shù)執(zhí)行完畢后,新創(chuàng)建對(duì)象與原有對(duì)象指向同一 U_Ptr 對(duì)象,該 U_Ptr 對(duì)象的使用計(jì)數(shù)加 1。
析構(gòu)函數(shù)將檢查 U_Ptr 基礎(chǔ)對(duì)象的使用計(jì)數(shù)。
如果使用計(jì)數(shù)為 0,則這是最后一個(gè)指向該 U_Ptr 對(duì)象的 HasPtr 對(duì)象,
在這種情況下,HasPtr 析構(gòu)函數(shù)刪除其 U_Ptr 指針。
刪除該指針將引起對(duì) U_Ptr 析構(gòu)函數(shù)的調(diào)用,U_Ptr 析構(gòu)函數(shù)刪除 int 基礎(chǔ)對(duì)象。
2.4 賦值與使用計(jì)數(shù)
賦值操作符比復(fù)制構(gòu)造函數(shù)復(fù)雜一點(diǎn):
HasPtr& HasPtr::operator=(const HasPtr &rhs){++rhs.ptr->use; // increment use count on rhs firstif (--ptr->use == 0)delete ptr; // if use count goes to 0 on this object, delete itptr = rhs.ptr; // copy the U_Ptr objectval = rhs.val; // copy the int memberreturn *this;}在這里,首先將右操作數(shù)中的使用計(jì)數(shù)加 1,然后將左操作數(shù)對(duì)象的使用計(jì)數(shù)減 1 并檢查這個(gè)使用計(jì)數(shù)。
如果這是指向 U_Ptr 對(duì)象的最后一個(gè)對(duì)象,就刪除該對(duì)象,這會(huì)依次撤銷(xiāo) int 基礎(chǔ)對(duì)象。
將左操作數(shù)中的當(dāng)前值減 1(可能撤銷(xiāo)該對(duì)象)之后,
再將指針從 rhs 復(fù)制到這個(gè)對(duì)象。賦值照常返回對(duì)這個(gè)對(duì)象的引用。
這個(gè)賦值操作符在減少左操作數(shù)的使用計(jì)數(shù)之前使 rhs 的使用計(jì)數(shù)加 1,從而防止自身賦值。
注意:如果左右操作數(shù)相同,賦值操作符的效果將是 U_Ptr 基礎(chǔ)對(duì)象的使用計(jì)數(shù)加 1 之后立即減 1。
2.5 改變其他成員
現(xiàn)在需要改變?cè)L問(wèn) int* 的其他成員,以便通過(guò) U_Ptr 指針間接獲取 int:
class HasPtr {public:// copy control and constructors as before// accessors must change to fetch value from U_Ptr objectint *get_ptr() const { return ptr->ip; }int get_int() const { return val; }// change the appropriate data membervoid set_ptr(int *p) { ptr->ip = p; }void set_int(int i) { val = i; }// return or change the value pointed to, so ok for const objects// Note: *ptr->ip is equivalent to *(ptr->ip)int get_ptr_val() const { return *ptr->ip; }void set_ptr_val(int i) { *ptr->ip = i; }private:U_Ptr *ptr; // points to use-counted U_Ptr classint val;};那些使用指針操作的函數(shù)必須對(duì) U_Ptr 解引用,以便獲取 int* 基礎(chǔ)對(duì)象。
復(fù)制 HasPtr 對(duì)象時(shí),int 成員的行為與第一個(gè)類(lèi)中一樣。
所復(fù)制的是 int 成員的值,各成員是獨(dú)立的,副本和原對(duì)象中的指針仍指向同一基礎(chǔ)對(duì)象,
對(duì)基礎(chǔ)對(duì)象的改變將影響通過(guò)任一 HasPtr 對(duì)象所看到的值。
然而,HasPtr 的用戶無(wú)須擔(dān)心懸垂指針,只要他們讓 HasPtr 類(lèi)負(fù)責(zé)釋放對(duì)象,
HasPtr 類(lèi)將保證只要有指向基礎(chǔ)對(duì)象的 HasPtr 對(duì)象存在,基礎(chǔ)對(duì)象就存在。
為了管理具有指針成員的類(lèi),
必須定義復(fù)制構(gòu)造函數(shù)、賦值操作符和析構(gòu)函數(shù)這三個(gè)復(fù)制控制成員。
值型類(lèi)將指針成員所指基礎(chǔ)值的副本給每個(gè)對(duì)象。
復(fù)制構(gòu)造函數(shù)分配新元素并從被復(fù)制對(duì)象處復(fù)制值,
賦值操作符撤銷(xiāo)所保存的原對(duì)象并從右操作數(shù)向左操作數(shù)復(fù)制值,析構(gòu)函數(shù)撤銷(xiāo)對(duì)象。?
“智能指針”的類(lèi)在對(duì)象間共享同一基礎(chǔ)值,從而提供了指針型行為。
使用計(jì)數(shù)是管理智能指針類(lèi)的通用技術(shù)。
同一基礎(chǔ)值的每個(gè)副本都有一個(gè)使用計(jì)數(shù)。
復(fù)制構(gòu)造函數(shù)將指針從舊對(duì)象復(fù)制到新對(duì)象時(shí),會(huì)將使用計(jì)數(shù)加 1。
賦值操作符將左操作數(shù)的使用計(jì)數(shù)減 1 并將右操作數(shù)的使用計(jì)數(shù)加 1,
如果左操作數(shù)的使用計(jì)數(shù)減至 0,賦值操作符必須刪除它所指向的對(duì)象,
最后,賦值操作符將指針從右操作數(shù)復(fù)制到左操作數(shù)。
析構(gòu)函數(shù)將使用計(jì)數(shù)減 1,并且,如果使用計(jì)數(shù)減至 0,就刪除基礎(chǔ)對(duì)象。
?[3. 定義值型類(lèi)]
處理指針成員的另一個(gè)方法是給指針成員提供值語(yǔ)義。
具有值語(yǔ)義的類(lèi)所定義的對(duì)象,其行為很像算術(shù)類(lèi)型的對(duì)象:
復(fù)制值型對(duì)象時(shí),會(huì)得到一個(gè)不同的新副本。
對(duì)副本所做的改變不會(huì)反映在原有對(duì)象上,反之亦然。string 類(lèi)是值型類(lèi)的一個(gè)例子。
要使指針成員表現(xiàn)得像一個(gè)值,復(fù)制 HasPtr 對(duì)象時(shí)必須復(fù)制指針?biāo)赶虻膶?duì)象:
復(fù)制構(gòu)造函數(shù)不再?gòu)?fù)制指針,它將分配一個(gè)新的 int 對(duì)象,并初始化該對(duì)象以保存與被復(fù)制對(duì)象相同的值。
每個(gè)對(duì)象都保存屬于自己的 int 值的不同副本,所以析構(gòu)函數(shù)將無(wú)條件刪除指針。
賦值操作符不需要分配新對(duì)象,
它只是必須記得給其指針?biāo)赶虻膶?duì)象賦新值,而不是給指針本身賦值:
return *this;}
換句話說(shuō),改變的是指針?biāo)赶虻闹?#xff0c;而不是指針。
即使要將一個(gè)對(duì)象賦值給它本身,賦值操作符也必須總是保證正確。
本例中,即使左右操作數(shù)相同,操作本質(zhì)上也是安全的,因此,不需要顯式檢查自身賦值。
轉(zhuǎn)載于:https://www.cnblogs.com/yshl-dragon/p/3227707.html
總結(jié)
- 上一篇: DRBD+HeartBeat+NFS 架
- 下一篇: 常用的web安全处理