C++中的智能指针:如何正确使用 std::shared_ptr 与 std::unique_ptr

在现代 C++ 开发中,手动管理内存已经逐渐被智能指针取代。智能指针能够自动处理资源生命周期,显著减少内存泄漏和悬空指针的风险。本篇文章将从概念、使用场景、常见陷阱以及最佳实践四个方面,对 std::shared_ptrstd::unique_ptr 做一次系统梳理。

一、智能指针概览

指针类型 所有权模型 典型使用场景
`std::unique_ptr
` 独占所有权 临时对象、单一所有者、资源包装器
`std::shared_ptr
` 共享所有权 对象需要多处共享、跨模块共享、递归结构

注意std::unique_ptr 采用“移动语义”,不支持拷贝;std::shared_ptr 采用引用计数,支持拷贝和移动,但需注意循环引用。

二、std::unique_ptr 的使用要点

1. 创建与转移

std::unique_ptr <Foo> p1(new Foo);   // 直接创建
auto p2 = std::make_unique <Foo>();   // 推荐方式,避免手动 new

std::unique_ptr <Foo> p3 = std::move(p1); // 转移所有权
  • 不要使用 new 直接构造 unique_ptrmake_unique 更安全、可读性更好。

2. 与容器配合

std::vector<std::unique_ptr<Foo>> vec;
vec.emplace_back(std::make_unique <Foo>());
  • 禁止unique_ptr 复制到容器中,必须使用 emplace_backpush_back(std::move(...))

3. 自定义删除器

std::unique_ptr<FILE, decltype(&fclose)> filePtr(fopen("file.txt", "r"), &fclose);
  • 当资源不是使用 new/delete 管理时,可以提供自定义删除器。

4. 循环引用预防

unique_ptr 本身不会导致循环引用,但与 std::shared_ptr 混用时要特别注意。例如,父子节点关系中,子节点用 unique_ptr 指向父节点,父节点用 shared_ptr 指向子节点,避免双向引用导致内存泄漏。

三、std::shared_ptr 的使用要点

1. 复制与引用计数

std::shared_ptr <Foo> sp1 = std::make_shared<Foo>();
std::shared_ptr <Foo> sp2 = sp1; // 计数 +1
  • 复制会共享所有权,计数会自动增加,销毁时计数递减。

2. 循环引用问题

struct Node {
    std::shared_ptr <Node> next;
    std::weak_ptr <Node> prev;   // 使用 weak_ptr 打破循环
};
  • weak_ptr 只观察而不计数,可通过 lock() 获得 shared_ptr

3. 线程安全

  • 标准库实现的 std::shared_ptr 的引用计数是原子操作,适合多线程场景,但对对象内部状态的同步需自行处理。

4. 性能注意

  • shared_ptr 需要两块内存:对象本身 + 控制块(计数)。如果频繁创建小对象,考虑使用 unique_ptr 或手工内存池。

四、智能指针的最佳实践

  1. 首选 std::unique_ptr
    只有在确实需要共享所有权时才使用 shared_ptr

  2. 避免裸指针与智能指针混用
    如果返回裸指针,明确其所有权归属,最好通过 std::shared_ptrstd::unique_ptr 明确返回类型。

  3. 在异常安全代码中使用
    智能指针天然符合 RAII,能在异常抛出时自动析构。

  4. 结合 std::optional 使用
    对可能为空的对象,使用 std::optional<std::unique_ptr<T>> 进行包装,避免返回裸指针。

  5. 使用 std::make_unique / std::make_shared
    统一资源创建方式,提升性能与可读性。

五、实战案例:资源包装器

class FileHandle {
    std::unique_ptr<FILE, decltype(&fclose)> file_;
public:
    explicit FileHandle(const std::string& path)
        : file_(fopen(path.c_str(), "r"), &fclose) {
        if (!file_) throw std::runtime_error("Open file failed");
    }
    // 提供读取接口
    std::string read() {
        std::string line, result;
        while (fgets(line, 1024, file_.get())) {
            result += line;
        }
        return result;
    }
};
  • FileHandle 内部使用 unique_ptr 自动管理 FILE*,无须手动关闭文件。

六、总结

  • std::unique_ptr:独占所有权,轻量级,适合单一拥有者场景。
  • std::shared_ptr:共享所有权,需关注循环引用。
  • 通过 make_unique/make_shared 创建,使用 weak_ptr 预防循环,结合异常安全的 RAII,能够让 C++ 程序更健壮、更易维护。

智能指针是 C++ 现代化内存管理的核心工具,正确使用它们可以极大减少内存错误,提升代码质量。祝编码愉快!

发表评论