c++中如何使用智能指针_c++ unique_ptr与shared_ptr用法【汇总】

8次阅读

该用 std::unique_ptr 而不是 std::shared_ptr 的核心判断标准是所有权是否需要共享:若对象生命周期明确、仅归一个作用域或类管理,就用 std::unique_ptr,因其轻量、移动语义清晰且防拷贝。

c++中如何使用智能指针_c++ unique_ptr与shared_ptr用法【汇总】

什么时候该用 std::unique_ptr 而不是 std::shared_ptr

核心判断标准是所有权是否需要共享。如果对象生命周期明确、只归一个作用域或类管理,就用 std::unique_ptr;它轻量(无引用计数开销)、移动语义清晰、且能防止意外拷贝。

常见误用场景:为图省事把所有动态对象都塞进 std::shared_ptr,结果引发循环引用或性能下降。

  • std::unique_ptr 不能拷贝,只能移动 —— 这是编译器强制的保护,别试图绕过
  • 工厂函数返回 std::unique_ptr 是推荐做法,调用方自然获得独占所有权
  • 成员变量优先用 std::unique_ptr 管理大对象或可选资源(比如延迟初始化的缓存)
  • 若需在多个地方观察同一对象但不参与生命周期管理,用裸指针std::weak_ptr 配合 std::shared_ptr

std::shared_ptr 的构造和引用计数陷阱

引用计数不是线程安全的“读写”,而是“修改操作”(如拷贝、赋值、析构)线程安全 —— 但对象本身的访问仍需额外同步。

最常踩的坑是用原始指针重复构造 std::shared_ptr

立即学习C++免费学习笔记(深入)”;

int* raw = new int(42); std::shared_ptr a(raw); std::shared_ptr b(raw); // ❌ 双重释放!a 和 b 各自维护一份引用计数

正确方式只有一种入口:

  • 始终用 std::make_shared(...) 构造(推荐,内存分配合并,效率高)
  • 必须从裸指针构造时,只调用一次 std::shared_ptr(raw),之后靠拷贝传播
  • 避免把 this 直接传给 std::shared_ptr 构造 —— 用 std::enable_shared_from_this 替代

std::unique_ptr 的自定义删除器怎么写才不崩溃

默认删除器是 delete,但遇到 C 风格资源(如 fopen/fclosemalloc/free)必须显式指定删除逻辑,否则会 UB。

关键点:删除器类型是 std::unique_ptr 模板的一部分,影响大小和 ABI 兼容性。

  • 函数指针形式最简洁:std::unique_ptr fp(fopen("x.txt", "r"), &fclose)
  • Lambda 若含捕获(如 [&]),就不能作为模板参数,得用 std::function —— 但会带来分配和虚调用开销
  • Struct 定义删除器时,确保 operator()noexcept,否则 std::unique_ptr 移动可能异常中止
  • 数组特化必须用 std::unique_ptr,否则 delete 被调用而非 delete[]

循环引用:为什么 std::shared_ptr 会“卡住”内存不释放

当两个对象互相持有对方的 std::shared_ptr 成员时,引用计数永远 >0,析构永远不会触发 —— 这就是循环引用。

典型场景:父子结构(如树节点)、观察者模式中的回调绑定。

  • 打破循环的唯一可靠方式是把其中一端换成 std::weak_ptr
  • std::weak_ptr 不增加引用计数,访问前必须调用 lock() 转成 std::shared_ptr,失败说明对象已销毁
  • 不要用 std::shared_ptr 管理 this,改用继承 std::enable_shared_from_this,再调用 shared_from_this()
  • 调试时可用 use_count() 打印引用数,但注意它非原子,仅作诊断参考

智能指针不是万能胶水,std::unique_ptrstd::shared_ptr 的选择本质是建模你对资源生命周期的理解。越早明确“谁创建、谁销毁、谁只是临时使用”,就越少掉进引用计数和删除器的坑里。

text=ZqhQzanResources