当前位置: 首页 > news >正文

Item28--避免返回 handles 指向对象内部成分

1. 什么是 "Handle"?

在这里,Handle(句柄)是一个广义的概念,不仅仅指 Windows API 中的 HANDLE。任何能够让你直接访问对象内部数据的媒介,都叫 Handle。

在 C++ 中,主要指这三样东西:

  1. 指针 (Pointers)
  2. 引用 (References)
  3. 迭代器 (Iterators)

如果你把这三样东西传给外部使用者,并且它们指向的是你的 private 成员变量,你就等于把自家大门的钥匙交给了陌生人。


2. 罪状一:破坏封装性 (即便加了 const 也没用)

这是最直观的后果。让我们看一个经典的例子:

假设我们要设计一个 Rectangle 类,它由两个点(左上角和右下角)决定。

class Point {
public:void setX(int x);void setY(int y);// ...
};struct RectData {Point ulhc; // Upper Left Hand CornerPoint lrhc; // Lower Right Hand Corner
};class Rectangle {
private:std::shared_ptr<RectData> pData; // 内部数据public:// 错误写法!// 这是一个 const 函数,意图是只读Point& upperLeft() const { return pData->ulhc; }
};

这里的逻辑陷阱:

  1. upperLeftconst 函数。编译器只保证在这个函数内部,你不会修改 pData 这个智能指针本身(即不改变它指向谁)。
  2. 但是,你返回了一个指向内部数据的引用Point&)。
  3. 后果: 调用者拿到了这个引用,就可以随意修改私有数据,完全无视 constprivate 的限制。
Point coord1(0, 0);
Point coord2(100, 100);
const Rectangle rec(coord1, coord2); // rec 是常对象!// 惊悚时刻:
rec.upperLeft().setX(50); 
// 这一行代码编译通过!rec 的内部数据被修改了!
// 虽然 rec 声明为 const,但我们通过“引用后门”修改了它的状态。

修正方案

如果你必须返回内部数据供读取,请加上 const

// 正确写法:返回 const 引用
const Point& upperLeft() const { return pData->ulhc; 
}

现在,调用者只能读,不能写,封装性得以保留。但... 这还不够,因为还有第二个更致命的问题。


3. 罪状二:悬空 Handle (Dangling Handles)

即使你返回的是 const 引用(解决了修改问题),你依然可能面临对象已死,引用还在的恐怖场景。这种情况通常发生在临时对象身上。

假设我们有一个函数,返回一个 Rectangle包围盒 (Bounding Box)

// 返回一个临时的 Rectangle 对象(右值)
Rectangle boundingBox(const GUIObject& obj); 

现在,用户写了这样一行代码:

// 获取包围盒的左上角坐标
const Point* pUpperLeft = &(boundingBox(someGUIObject).upperLeft());

让我们慢动作回放这里发生了什么:

  1. 调用 boundingBox:产生了一个临时的 Rectangle 对象(Temporary Object)。
  2. 调用 upperLeft:这个临时对象返回了一个指向它内部 Point 的引用。
  3. 取地址 &:指针 pUpperLeft 保存了这个内部 Point 的地址。
  4. 语句结束 (😉:这是关键!临时对象 Rectangle 在这行语句结束时被销毁(析构)。 它内部的 Point 也随之销毁。
  5. 结局pUpperLeft 现在指向了一块已经被回收的内存。它变成了悬空指针 (Dangling Pointer)。任何后续的使用都会导致未定义行为(Undefined Behavior)。

这就是为什么要“尽可能避免返回指向内部的 handle”。 只要 handle 传出去了,你就无法控制它的生命周期是否会长于对象本身。


4. 什么时候可以打破这个规则?

虽然 Item 28 叫我们要“避免”,但有些特殊情况是例外。最典型的就是 STL 容器字符串

  • std::vector::operator[]
  • std::string::operator[]

这些操作符必须返回指向内部元素的引用,以便我们进行读写(比如 v[0] = 10;)。但这是基于一种隐式的契约:程序员知道容器销毁后,指向元素的引用也会失效。

但在我们要编写的业务类(Application Classes)中,通常不应该留这种口子。


总结 (Summary)

  1. Handle 定义: 指针、引用、迭代器都是 Handle。
  2. 核心风险:
    • 破坏封装: 外部可以直接修改私有数据(即使是在 const 对象上)。
    • 悬空引用: 如果对象是临时的,返回的 Handle 会在语句结束后指向无效内存,引发 Crash。
  3. 解决方案:
    • 首选: 如果对象不大,返回值 (Return by Value),这最安全,完全切断了外部与内部的物理联系。
    • 次选: 如果必须返回引用以提高效率,务必返回 const 引用,且要极其小心调用端的生命周期问题。
http://www.jsqmd.com/news/116000/

相关文章:

  • 前端开发随笔
  • 基于java的SpringBoot/SSM+Vue+uniapp的大学生学业预警和警告平台的详细设计和实现(源码+lw+部署文档+讲解等)
  • 一文吃透动态规划解题思路——以钢条切割问题为例
  • Redis:安装配置、核心概念与实践应用
  • 程序员的幸福之道:不必追逐权力与学历——在代码与生活之间寻找真正的自由
  • 别再焦虑了!6款实测有效的降ai工具推荐,学姐手把手教你降低ai率!
  • 孩子近视的“真凶”不是手机,也不是电视,而是父母都不在意的它
  • 基于java的SpringBoot/SSM+Vue+uniapp的课程目标达成度系统的详细设计和实现(源码+lw+部署文档+讲解等)
  • 动态规划解决最小编辑距离问题
  • 拒绝智商税!6款实测有效的降ai工具推荐,保姆级手把手教你降低ai率!
  • dockerfile多阶段构建 + UBI 9(在指定的根目录中卸载 setup 包`rpm --root /mnt/rootfs -e --nodeps setup`)
  • 防控近视你需要知道的这些科普常识!
  • 基于java的SpringBoot/SSM+Vue+uniapp的本科生毕业论文管理系统的详细设计和实现(源码+lw+部署文档+讲解等)
  • 别花冤枉钱!6款实测有效的降ai工具推荐,0基础手把手教你降低ai率!
  • Item23--宁以 non-member、non-friend 替换 member 函数
  • 无金融背景想入行?2025年靠这几张AI证书实现转行突破
  • 【Memory协议栈】AUTOSAR架构下NvM_ReadAll时间优化的实用方案
  • 实习期忙到飞?2025职场新人低成本学习AI生存指南
  • 电池管理系统BMS
  • 今天,终于进博客园了
  • 今天,终于进博客园了
  • 基于java的SpringBoot/SSM+Vue+uniapp的心理咨询预约管理的详细设计和实现(源码+lw+部署文档+讲解等)
  • Nginx负载均衡策略详解与Session一致性解决方案
  • Item34--区分接口继承和实现继承
  • 【2025避坑指南】10款常见降AI率工具大汇总(含真实有效的免费降AI版本)
  • Item24--若所有参数皆需类型转换,请为此采用 non-member 函数
  • 基于java的SpringBoot/SSM+Vue+uniapp的赛车爱好者交流平台的详细设计和实现(源码+lw+部署文档+讲解等)
  • Item18--让接口容易被正确使用,不易被误用
  • 算法日记专题:位运算II( 只出现一次的数字I II III 面试题:消失的两个数字 比特位计数)
  • PyTorch - 指南