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

item13--使用对象管理资源

简单来说,不要手动去 delete 指针或释放资源,而是把资源放进一个对象里,依靠对象的析构函数(Destructor)自动释放资源。

以下是该条款的详细深度解析,结合了原书内容与现代 C++(C++11 及以后)的最佳实践。


1. 为什么我们需要“以对象管理资源”?

传统做法的缺陷

假设我们有一个工厂函数,用于创建一个 Investment(投资)对象:

Investment* createInvestment(); // 返回指针,指向动态分配的对象

传统的调用方式如下:

void f() {Investment* pInv = createInvestment(); // 1. 获取资源// ... 执行一些操作 ...delete pInv;                           // 2. 显式释放资源
}

潜在的风险: 如果在“执行一些操作”的过程中发生了以下情况,delete pInv 永远不会被执行,从而导致内存泄漏

  1. 提前返回(Early return):代码逻辑中有一个 return 语句。
  2. 抛出异常(Exception):中间的代码抛出了异常,栈展开(Stack unwinding)跳过了 delete。

解决方案:RAII

C++ 保证:当一个对象离开作用域(Scope)时,其析构函数会被自动调用。

因此,我们将资源(pInv)包装在一个局部对象中。当函数 f() 结束时,局部对象自动销毁,其析构函数负责调用 delete


2. 核心原则 (RAII 的两大铁律)

Scott Meyers 在书中总结了两个关键点:

  1. 获得资源后立刻放进管理对象 (Resource Acquisition Is Initialization)
    • 当我们调用 createInvestment() 拿到指针的那一刻,应该立刻把它传递给智能指针的构造函数。
    • 不要让裸指针在外面“裸奔”。
  2. 管理对象运用析构函数确保资源被释放
    • 无论控制流是如何离开作用域的(正常结束、break、return、throw),析构函数都会被调用,从而保证资源不泄漏。

3. 书中介绍的工具与现代演变

《Effective C++》成书较早,书中主要介绍了 std::auto_ptrtr1::shared_ptr。在现代 C++ (C++11+) 中,情况发生了变化。

A. std::auto_ptr (已废弃/移除)

  • 书中的描述:它是早期的智能指针,当它被复制时(通过 copy 构造或 copy assignment),它会转移所有权,原来的指针变成 null
  • 现代观点绝对不要再使用 auto_ptr。它在 C++11 中被标记为废弃,在 C++17 中已被彻底移除。它的“复制即转移”语义非常容易导致 Bug(例如在 STL 容器中使用会导致未定义行为)。
  • 替代者std::unique_ptr

B. std::unique_ptr (现代首选)

这是 auto_ptr 的完美继任者。

  • 语义:专属所有权(Exclusive Ownership)。同一时间只能有一个 unique_ptr 指向该对象。
  • 特点:禁止复制(Copy),只允许移动(Move)。这完美契合了“独占”的逻辑,且性能几乎等同于裸指针。

代码示例:

#include <memory>void f() {// 使用 unique_ptr 管理资源std::unique_ptr<Investment> pInv(createInvestment()); // ... 做任何操作 ...// ... 哪怕抛出异常 ...} // 函数结束,pInv 离开作用域,自动调用 delete

C. std::shared_ptr (引用计数)

书中提到了 RCSP (Reference-counting smart pointer)。

  • 语义:共享所有权。多个指针可以指向同一个对象。
  • 原理:内部维护一个引用计数器。
    • 复制时,计数 +1。
    • 析构时,计数 -1。
    • 当计数变为 0 时,真正的 delete 被调用。
  • 适用场景:当多个对象需要共享底层资源,且无法确定谁最后使用完该资源时。

4. 这里的“资源”不仅仅是内存

虽然本条款主要用内存(指针)举例,但 RAII 适用于所有必须释放的资源:

  • 文件句柄 (File descriptors)
  • 互斥锁 (Mutex locks)
  • 数据库连接 (Database connections)
  • 网络套接字 (Network sockets)

例子:管理互斥锁 不要手动 lock()unlock()

void strictCode() {std::mutex m;std::lock_guard<std::mutex> lock(m); // 构造时 lock// ... 操作共享数据 ...
} // 作用域结束,lock 析构,自动 unlock

5. 特别警示:关于数组

条款 13 特别提到一点:智能指针默认的删除器是 delete,而不是 delete[]

如果你这样做(在老式 C++ 中):

std::auto_ptr<std::string> aps(new std::string[10]); // 错误!

aps 析构时,它会执行 delete 而不是 delete[],导致未定义行为(通常是内存泄漏或崩溃)。

现代 C++ 的建议:

  1. 优先使用 std::vectorstd::string:它们内部已经封装了数组管理的逻辑,几乎不需要手动 new 数组。

  2. 如果非要用智能指针管理数组,C++11 后的 unique_ptr 支持数组特化:

    std::unique_ptr<int[]> up(new int[10]); // 正确,会自动调用 delete[]
    

总结

条款 13 的核心教义是:为了防止资源泄漏,请使用 RAII 对象。

对于现代 C++ 开发者 (你) 的行动指南:

  1. 默认使用 std::unique_ptr:如果资源是独占的。
  2. 需要共享时使用 std::shared_ptr:如果资源需要在多个拥有者之间共享。
  3. 永远不要使用 std::auto_ptr
  4. 优先使用标准容器 (vector, string) 代替动态分配的数组 (new T[])。
http://www.jsqmd.com/news/115940/

相关文章:

  • 深入解析:蓝桥杯基础算法精讲:模拟与高精度运算实战指南
  • item12-- 拷贝一个对象的所有组成部分
  • sub_match
  • sub_match
  • 抽奖机随机号码生成:3 种算法实现 + 测试全解析(附完整代码)
  • 【零基础精通】Python 字符串全解析:从字符序列到不可变对象的深度构建
  • item14--谨慎考虑资源管理类的拷贝行为
  • python django flask酒店客房管理系统数据可视化分析系统_gq8885n3--论文md5
  • python django flask鹿幸公司员工食堂在线点餐餐饮餐桌预约管理系统的设计与实现_utcnqqs0--论文
  • error_code
  • 虚拟化初步了解
  • Miloco 深度打通 Home Assistant,实现设备级精准控制
  • 好用的大型牛场水滴粉碎机技术强的
  • set_value
  • 日记1217
  • function的类型擦除
  • function bind
  • 日记12,19
  • Item10--令赋值操作符返回一个
  • Item9--绝不在构造和析构过程中调用虚函数
  • python django flask考研互助交流平台_c62p51fu--论文
  • 日记12.18
  • 离散化遍历
  • Ubuntu上使用VScode创建Maven项目
  • 线程(2)
  • 大规模语言模型的抽象思维与创新能力培养
  • 线程(1)
  • 方达炬〖发明超新技术〗:冰堆技术;冷极冰堆建筑技术;
  • Item6--若不想使用编译器自动生成的函数,就该明确拒绝
  • 我发现LLM解析基因数据优化抗癌药剂量,患者副作用直降40%