条款28:避免返回 handle 指向对象内部成分

Effective C++ 中文版 第三版》读书笔记

** 条款 28:避免返回 handle 指向对象内部成分 **

假设程序涉及矩形。为了让 Rectangle 对象尽可能小,可能把定义矩形的点放在一个辅助的 struct 内再让 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; 
};

这样的设计可以通过编译,但却是错误的。实际上自相矛盾,一方面 upperleft 和 lowerRight 被声明为 const,不让客户修改 Rectangle。另一方面,这两个函数都返回 reference 指向 private 数据,调用者可以通过这些 reference 更改内部数据:

Point coord1(0,0); 
Point coord2(100,100); 
const Rectangle rec(coord1, coord2); 
rec.upperLeft().setX(50); // 现在 rec 变成从 (50,0) 到 (100,100)

第一,成员变量的封装性最多只等于 “返回其 reference” 的函数的访问级别。
第二,如果 const 成员函数传出一个 reference,后者所指数据与对象自身有关,而它又被存储在对象之外,那么这个函数的调用者可以修改那笔数据。这正是 bitwise constness 的一个附带结果,条款 3。

如果它们返回的是指针或迭代器,相同的结果还会发生,原因相同。reference、指针和迭代器统统都是所谓的 handles(号码牌,用来取得某个对象),而返回一个“代表对象内部数据的 handle”,随之而来的便是 “降低对象封装性” 的风险。同时,也可能造成 “虽然调用 const 成员函数却造成对象状态被更改”。

通常我们认为,对象的“内部”就是指它的成员变量,其实不被公开使用的成员函数(protected 或 private)也是对象 “内部” 的一部分,所以也不该返回它们的 handles。否则,它们的访问级别就会提高到返回它们的成员函数的访问级别。

上述两个问题可以在它们的返回类型上加上 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 不在是个谎言。至于封装性,这里是蓄意放松封装,有限度的放松:只让渡读取权,涂写权是禁止的。

即使这样,返回 “代表对象内部” 的 handles,有可能在其他场合导致 dangling handles(空悬的号码牌):

这种 handle 所指东西(的所属对象)不复存在。这种 “不复存在的对象” 最常见的来源就是函数返回值。

class GUIObject{...}; 

const Rectangle boundingBox(const GUIObject& obj);

现在,客户可能这么使用这个函数:

GUIObject *pgo; 

... 

const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());//取得一个指针指向外框左上点

对 boundingBox 的调用获得一个新的、暂时的 Rectangle 对象,这个对象没有名称,权且称它为 temp。随后 upperLeft 作用于 temp 对象身上,返回 reference 指向 temp 的一个内部成分。具体指向 temp 的那个 Point 对象。但是这个语句结束之后,boundingBox 的返回值,也就是我们所说的 temp,将被销毁,而那间接导致 temp 内的 Points 析构。最终导致 pUpperLeft 指向一个不再存在的对象;变成空悬、虚吊(dangling)!

只要 handle 被传出去了,不管这个 handle 是不是 const,也不论返回 handle 的函数是不是 const。这里的唯一关键是暴露在 “handle 比其所指对象更长寿” 的风险下。

** 请记住: **
避免返回 handle(包括 reference、指针、迭代器)指向对象内部。遵守这个条款可增加封装性,帮助 const 成员函数的行为像个 const,并将发生“虚吊号码牌”的可能性降至最低。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容