《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ā)生“虛吊號碼牌”的可能性降至最低。