條款28:避免返回 handle 指向?qū)ο髢?nèi)部成分

Effective C++ 中文版 第三版》讀書筆記

** 條款 28:避免返回 handle 指向?qū)ο髢?nèi)部成分 **

假設(shè)程序涉及矩形。為了讓 Rectangle 對象盡可能小,可能把定義矩形的點(diǎn)放在一個(gè)輔助的 struct 內(nèi)再讓 Rectangle 去指它:

class Point{ 
public: 
    Point(int x, int y); 
    ... 
    void setX(int newVal); 
    void setY(int newVal); 
    ... 
}; 

struct RectData{ 
    Point ulhc; 
    Point lrhc; 
}; 

class Rectangle{ 
    ... 
    Point& upperLeft()const {return pData->ulhc;} 
    Point& lowerRight()const {return pData->lrhc;} 

private: 
    std::tr1::shared_ptr<RectData> pData; 
};

這樣的設(shè)計(jì)可以通過編譯,但卻是錯(cuò)誤的。實(shí)際上自相矛盾,一方面 upperleft 和 lowerRight 被聲明為 const,不讓客戶修改 Rectangle。另一方面,這兩個(gè)函數(shù)都返回 reference 指向 private 數(shù)據(jù),調(diào)用者可以通過這些 reference 更改內(nèi)部數(shù)據(jù):

Point coord1(0,0); 
Point coord2(100,100); 
const Rectangle rec(coord1, coord2); 
rec.upperLeft().setX(50); // 現(xiàn)在 rec 變成從 (50,0) 到 (100,100)

第一,成員變量的封裝性最多只等于 “返回其 reference” 的函數(shù)的訪問級別。
第二,如果 const 成員函數(shù)傳出一個(gè) reference,后者所指數(shù)據(jù)與對象自身有關(guān),而它又被存儲(chǔ)在對象之外,那么這個(gè)函數(shù)的調(diào)用者可以修改那筆數(shù)據(jù)。這正是 bitwise constness 的一個(gè)附帶結(jié)果,條款 3。

如果它們返回的是指針或迭代器,相同的結(jié)果還會(huì)發(fā)生,原因相同。reference、指針和迭代器統(tǒng)統(tǒng)都是所謂的 handles(號碼牌,用來取得某個(gè)對象),而返回一個(gè)“代表對象內(nèi)部數(shù)據(jù)的 handle”,隨之而來的便是 “降低對象封裝性” 的風(fēng)險(xiǎn)。同時(shí),也可能造成 “雖然調(diào)用 const 成員函數(shù)卻造成對象狀態(tài)被更改”。

通常我們認(rèn)為,對象的“內(nèi)部”就是指它的成員變量,其實(shí)不被公開使用的成員函數(shù)(protected 或 private)也是對象 “內(nèi)部” 的一部分,所以也不該返回它們的 handles。否則,它們的訪問級別就會(huì)提高到返回它們的成員函數(shù)的訪問級別。

上述兩個(gè)問題可以在它們的返回類型上加上 const 即可:

class Rectangle{ 
    ... 
    const Point& upperLeft()const {return pData->ulhc;} 
    const Point& lowerRight()const {return pData->lrhc;} 

private: 
    std::tr1::shared_ptr<RectData> pData; 
};

const 不在是個(gè)謊言。至于封裝性,這里是蓄意放松封裝,有限度的放松:只讓渡讀取權(quán),涂寫權(quán)是禁止的。

即使這樣,返回 “代表對象內(nèi)部” 的 handles,有可能在其他場合導(dǎo)致 dangling handles(空懸的號碼牌):

這種 handle 所指東西(的所屬對象)不復(fù)存在。這種 “不復(fù)存在的對象” 最常見的來源就是函數(shù)返回值。

class GUIObject{...}; 

const Rectangle boundingBox(const GUIObject& obj);

現(xiàn)在,客戶可能這么使用這個(gè)函數(shù):

GUIObject *pgo; 

... 

const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());//取得一個(gè)指針指向外框左上點(diǎn)

對 boundingBox 的調(diào)用獲得一個(gè)新的、暫時(shí)的 Rectangle 對象,這個(gè)對象沒有名稱,權(quán)且稱它為 temp。隨后 upperLeft 作用于 temp 對象身上,返回 reference 指向 temp 的一個(gè)內(nèi)部成分。具體指向 temp 的那個(gè) Point 對象。但是這個(gè)語句結(jié)束之后,boundingBox 的返回值,也就是我們所說的 temp,將被銷毀,而那間接導(dǎo)致 temp 內(nèi)的 Points 析構(gòu)。最終導(dǎo)致 pUpperLeft 指向一個(gè)不再存在的對象;變成空懸、虛吊(dangling)!

只要 handle 被傳出去了,不管這個(gè) handle 是不是 const,也不論返回 handle 的函數(shù)是不是 const。這里的唯一關(guān)鍵是暴露在 “handle 比其所指對象更長壽” 的風(fēng)險(xiǎn)下。

** 請記?。?**
避免返回 handle(包括 reference、指針、迭代器)指向?qū)ο髢?nèi)部。遵守這個(gè)條款可增加封裝性,幫助 const 成員函數(shù)的行為像個(gè) const,并將發(fā)生“虛吊號碼牌”的可能性降至最低。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容