C++11 智能指针完全指南

一、引言

C++11 标准引入了一套强大的内存管理工具——智能指针,主要包括 unique_ptrshared_ptrweak_ptr 三种[[1]]。这些智能指针都包含在 <memory> 头文件中,用于管理动态内存,确保资源被正确释放,从根本上防止内存泄漏[[2]]。


二、智能指针概述

智能指针是一种自动管理内存的指针,内部包含真实指针和引用计数,通过引用计数实现自动释放内存[[3]]。在传统的C++编程中,程序员需要手动使用newdelete来管理内存,这容易出错,可能导致内存泄漏或悬空指针。智能指针将这一过程交给编译器自动处理,大大解放了程序员手动释放内存的压力[[4]]。


三、unique_ptr:独占所有权

3.1 基本概念

unique_ptr 是一种独占智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全性[[5]]。每个 unique_ptr 指针指向的堆内存空间的引用计数都只能为1,一旦该指针放弃对所指堆内存空间的所有权,则该空间会被立即释放回收[[6]]。

3.2 核心特性

  • 不可复制unique_ptr 不支持拷贝构造和拷贝赋值[[7]]
  • 支持移动:可通过 std::move 转移所有权[[8]]
  • 独占性:确保同一时间只有一个指针拥有对象[[9]]
  • 默认释放器:使用 default_delete 释放内存[[10]]

3.3 基本用法示例

#include <memory>
#include <iostream>

std::unique_ptr<int> pInt = std::make_unique<int>(5);  // C++14引入
std::unique_ptr<int> pInt(new int(5));  // C++11方式

// 尝试复制会报错(编译失败)
// std::unique_ptr<int> pCopy = pInt;  // 错误!

// 正确:移动所有权
std::unique_ptr<int> pOwner = std::move(pInt);

3.4 应用场景

  • 函数返回无需同时存在于不同位置的对象
  • 资源所有权明确,需要严格控制的场景
  • 存储于容器中(如 std::vector<std::unique_ptr<T>>)[[11]]

四、shared_ptr:共享所有权

4.1 基本概念

shared_ptr 通过引用计数实现共享所有权,多个 shared_ptr 可共享同一对象,资源在最后一个引用销毁时自动释放[[12]]。

4.2 核心机制

  • 引用计数:当有新的 shared_ptr 指向同一个对象时,引用计数加1[[13]]
  • 自动释放:当 shared_ptr 离开作用域时,引用计数减1;当计数为0时,释放所管理的内存[[14]]
  • 支持拷贝和移动:可以进行拷贝构造和移动构造[[15]]

4.3 基本用法示例

#include <memory>
#include <iostream>

// 推荐方式:使用 make_shared
std::shared_ptr<int> sp = std::make_shared<int>(10);

// 传统方式:通过 new 初始化(不推荐)
// std::shared_ptr<int> sp(new int(10));

// 复制增加引用计数
std::shared_ptr<int> sp2 = sp;

// 查看引用计数
std::cout << sp.use_count() << std::endl;  // 输出:2

// 获取原始指针(慎用)
int* rawPtr = sp.get();

// reset 释放当前对象
sp.reset();

4.4 make_shared 的优势

make_shared 是常用的辅助函数模板,可以创建被保护的内存对象并返回 shared_ptr 对象[[16]]。与直接 new 相比,make_shared 具有更好的性能:

  • 减少内存分配次数(一次性分配对象和引用计数)
  • 提高异常安全性
  • 代码更简洁

4.5 注意事项

  • 避免多个 shared_ptr 管理同一内存:不要将同一个原始指针初始化为多个 shared_ptr,否则会导致重复释放[[17]]
  • 避免循环引用:两个或多个 shared_ptr 相互引用会导致引用计数永远不为0,引发内存泄漏[[18]]
  • 慎用 get():调用 get() 返回原始指针后需谨慎,避免意外释放[[19]]
  • 管理 this 指针:不要直接用 shared_ptr 管理类的 this 指针[[20]]

五、weak_ptr:观察者的选择

5.1 基本概念

weak_ptr 是一种弱引用指针,与 shared_ptr 的强引用不同,它不会增加引用计数[[21]]。weak_ptr 用于观察由 shared_ptr 管理的对象,防止循环引用导致的内存泄漏[[22]]。

5.2 核心特性

  • 不增加引用计数weak_ptr 本身不控制对象生命周期[[23]]
  • 可检查有效性:通过 expired() 方法检查资源是否已被释放[[24]]
  • 获取 shared_ptr:通过 lock() 方法获取指向原始对象的 shared_ptr[[25]]
  • 解决循环引用:将其中一个 shared_ptr 替换为 weak_ptr 可解决循环引用问题[[26]]

5.3 基本用法示例

#include <memory>
#include <iostream>

std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp = sp;  // 创建 weak_ptr

// 检查是否有效
if (!wp.expired()) {
    std::shared_ptr<int> sp2 = wp.lock();  // 获取 shared_ptr
    if (sp2) {
        std::cout << *sp2 << std::endl;
    }
}

// 当 sp 被释放后,wp.expired() 会返回 true
sp.reset();

5.4 循环引用问题示例

struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // 使用 weak_ptr 打破循环
};

// 如果两个 shared_ptr 互相指向,引用计数永不为0,造成内存泄漏
// 解决方案:将反向引用改为 weak_ptr

六、智能指针使用最佳实践

6.1 优先使用智能指针

  • 提高代码安全性
  • 增强可维护性
  • 减少内存泄漏风险[[27]]

6.2 选择合适类型

场景 推荐类型 理由
独占所有权 unique_ptr 明确、高效、无额外开销
共享所有权 shared_ptr 自动引用计数管理
非拥有者观察 weak_ptr 避免循环引用

6.3 重要注意事项[[28]]

  1. 不混合使用:避免将智能指针与普通裸指针混用
  2. 避免重复管理:不要将同一块内存交给多个智能指针管理
  3. 优先使用工厂函数:优先使用 make_shared/make_unique
  4. 注意线程安全shared_ptr 的引用计数操作是线程安全的,但对象本身不是

七、总结

C++11 智能指针是现代 C++ 内存管理的核心工具[[29]]:

  • unique_ptr 提供独占所有权,确保内存只被释放一次[[30]]
  • shared_ptr 提供共享所有权,通过引用计数保证对象存活[[31]]
  • weak_ptr 是非拥有者观察者,不能直接解引用,但可通过 lock() 方法临时访问共享对象[[32]]

正确使用智能指针能确保动态内存被正确释放,无论程序是否正常结束[[33]]。智能指针在提高代码安全性和可维护性方面发挥着重要作用,是现代 C++ 开发中不可或缺的工具[[34]]。