Effective C++ 条款28 避免返回handles指向对象内部成分
条款28 : 避免返回handles指向对象内部成分
让我们先来明确本条款中的两个概念 :
- handle : 即句柄, 号码牌, 可以理解为各种指针, 引用, 迭代器.
- 内部成分 : private成员变量和成员函数.
有了上面两个概念, 就可以比较直观地理解本条款了, 不过在通读完本条款后, 本条款虽说是避免返回handles指向对象内部成分, 但是其实内容着重在解释在必须返回handles指向对象内部成分的情况下, 会带来什么样的风险, 以此告诫我们注意.
降低对象封装性
书中指出, 返回handles指向对象内部成分, 随之而来的便是**”降低对象封装性”的风险**, 如果不是有意设计, 我们不应令public成员函数返回一个handle指向private的成员变量, 这会使后者的实际访问级别变为public, 这是完全可以理解的.
书中描述了GUI中常有的矩形, 它一般会用左上点和右下点表示 :
1 |
|
客户一般会要求使用矩形的位置信息 :
1 |
|
我们用两个函数分别返回左上点和右下点, 这个操作很正常, 但是这给了客户捣乱的方式 :
1 |
|
这种情况在条款3中也实际发生过, 就是通过const成员函数的返回值修改了类的内部数据, 原因条款3中已经解释过了, 解决方式也很简单, 给返回值也加一个const
就好了 :
1 |
|
重新整理思路, 封装性在于数据隐藏, 在于限制外部对内部的访问与修改. 以上函数做到了访问权的让渡与修改权的禁止, 访问权让渡是因为有必要的客户需求, 修改权禁止是应为客户没有权限修改内部, 以此在提供必要功能的前提下使对象达到了最好的封装性.
空悬句柄(dangling handles)
空悬句柄和C中的野指针很相似, 书中给出了某个函数返回GUI对象的外框(矩形)的例子 :
1 |
|
我们来解释最后一句代码 :
boundingBox(*pgo)
利用pgo
调用boundingBox
函数boundingBox
函数返回一个临时矩形对象(这是一个匿名对象, 以下简称temp
)temp
调用upperLeft()
得到该临时对象的左上点- 取出右上点的地址赋值给
pUpperLeft
最后的结果就是pUpperLeft
获得了一个来自临时对象的指针, 当控制域离开该行, 这个指针将成为一个野指针, 则称pUpperLeft
是一个空悬句柄, 它指向了一个不存在的对象.
因此书中告诉我们, 返回handles指向对象内部成分总是危险的, 不管这个handle
是否为const
唯一造成危险的事实就是, 有个handle被传出去了, 因此就有可能出现handle比其所指对象更长寿的风险, 这才是问题的核心.
然而指出这个风险不是说就不应该返回handles
, 我们总会有许多需求需要访问内部成分, 这种风险避无可避, 而是在告诫我们时常注意空悬句柄问题, 不要让我们的指针/引用/迭代器因为比其所指对象更长寿而失效.
我们可以再通过一个例子来加深理解 :
1 |
|
这个就是非常经典迭代器失效问题, 这个例子在遍历v
, 将v
中等于3的元素删除, 这段代码看似合理, 但是结合我们上面的理解, 在触发erase
后, it
迭代器指向的对象其实已经被销毁了, 这时++it
就变为了未定义的操作, 在vs
中甚至会直接报错, 如果我们可以提前发现这个问题, 就可以做出以下改进 :
1 |
|
我们利用erase
的返回值对it
重新赋值, 使其免于失效.
请记住 :
- 尽可能避免返回
handles
指向对象内部成分, 这可以提升对象封装性, 避免dangling handles
出现. - 避无可避时谨慎释出内部成分的访问权与修改权, 修改权可用
const
禁止, 注意dangling handles
问题.
by 天目中云