C++ 中的 std::shared_ptr 与 std::unique_ptr 的区别与使用场景

在 C++ 现代编程中,智能指针是管理动态内存的核心工具。标准库提供了两种最常用的智能指针:std::unique_ptrstd::shared_ptr。它们各自承担不同的职责,选择合适的指针能够既提高代码安全性,又保持性能。本文将从概念、内存管理、所有权语义、线程安全、以及典型使用场景等方面,对两者进行对比与说明,并给出实战建议。

1. 基本概念

指针 所有权类型 是否可复制 是否可移动
`std::unique_ptr
` 独占所有权
`std::shared_ptr
` 共享所有权
  • 独占所有权:一个 unique_ptr 在同一时刻只能有一个拥有者,所有权转移只能通过 std::move 完成。
  • 共享所有权:一个 shared_ptr 可以有多个指向同一对象的实例,内部维护引用计数,计数为 0 时自动销毁对象。

2. 内存管理

  • unique_ptr:在作用域结束或显式 reset() 时,立即调用 delete 释放资源。无需额外的引用计数开销。
  • shared_ptr:维护一个计数器(通常与对象一起存放在分配的块中)。每一次复制会增加计数,销毁时递减。计数为 0 时才销毁对象。

因此,unique_ptr 在单线程或确定唯一所有者的场景下性能更佳;shared_ptr 适合需要多处共享生命周期的情况,但需承担计数器的读写开销。

3. 所有权语义

  • 移动语义unique_ptr 必须移动才能转移所有权;shared_ptr 可以通过复制共享所有权。
  • 不可复制unique_ptr 禁止复制,以防止出现两份指针指向同一资源而导致双重删除。shared_ptr 允许复制,但内部同步引用计数,避免双删。

4. 线程安全

  • unique_ptr:其析构过程不是线程安全的。若多个线程持有同一 unique_ptr,必须自己同步。
  • shared_ptr:计数器的增减是线程安全的(使用原子操作)。但对象本身的状态不受保护,需要外部同步。

5. 典型使用场景

场景 推荐指针
单例/生命周期由创建者决定 unique_ptr
所有权转移(例如工厂返回对象) unique_ptr
需要共享生命周期(如观察者模式、事件系统) shared_ptr
对象需要在多线程间共享 shared_ptr + 外部锁或 std::atomic
管理数组 unique_ptr<T[]>(推荐)或 shared_ptr + 自定义删除器
与 C 风格 API 对接(需要裸指针或非所有权) unique_ptr + std::weak_ptr 结合

6. 常见陷阱与建议

  1. 循环引用
    shared_ptr 在存在循环引用(A->B, B->A)时会导致内存泄漏。使用 std::weak_ptr 破坏循环。

  2. 性能评估
    shared_ptr 的引用计数是原子操作,导致多线程下的缓存失效。若不需要共享,尽量使用 unique_ptr

  3. 自定义删除器
    对于非 new 分配的资源(如文件句柄、网络 socket),可以在 unique_ptrshared_ptr 中提供自定义删除器。

  4. 不可用 make_shared 的场景
    make_shared 会把计数器和对象一起分配,减少一次内存分配。若需要 unique_ptr 或自定义删除器,则需手动 new

  5. 避免裸指针逃逸
    在公共接口中尽量返回 shared_ptrunique_ptr,不要暴露裸指针。

7. 代码示例

// unique_ptr 示例
std::unique_ptr <MyClass> create()
{
    return std::make_unique <MyClass>();
}

// shared_ptr 示例
std::shared_ptr <MyClass> getShared()
{
    static std::weak_ptr <MyClass> cache;
    auto ptr = cache.lock();
    if (!ptr) {
        ptr = std::make_shared <MyClass>();
        cache = ptr;
    }
    return ptr;
}

在上述 create 函数中,所有权始终归于调用者;getShared 通过 weak_ptr 实现单例缓存,避免循环引用。

8. 结语

std::unique_ptrstd::shared_ptr 并不是互斥关系,而是根据所有权需求进行组合使用的工具。正确理解它们的所有权语义、性能特性和线程安全机制,能帮助开发者写出既安全又高效的 C++ 代码。随着 C++20/23 的继续演进,智能指针的使用已成为现代 C++ 编程的基础。祝你编码愉快!

发表评论