惠州市网站建设_网站建设公司_UI设计师_seo优化
2025/12/20 21:44:56 网站建设 项目流程

简单来说,不要手动去 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[])。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询