6月1日 09:35

C++ 智能指针怎么用?unique_ptr/shared_ptr/weak_ptr 区别与陷阱

C++ 智能指针通过 RAII 原则自动管理堆内存生命周期:对象在构造时获取资源,析构时释放资源,即使异常抛出也能保证析构函数执行。unique_ptr 表达独占所有权,不可拷贝只能移动,零开销抽象,性能等价于裸指针;shared_ptr 表达共享所有权,内部维护引用计数,最后一个 shared_ptr 析构时释放资源,引用计数操作是原子的所以线程安全,但指向对象本身的并发访问需要额外同步;weak_ptrshared_ptr 的弱引用观察者,不增加引用计数,用来打破循环引用和实现缓存/观察者模式。优先用 make_unique/make_shared 创建智能指针:make_shared 一次分配同时构造对象和控制块,减少内存碎片且异常安全;直接 new 传给 shared_ptr 构造函数则分两次分配,且在函数参数求值顺序未定义时可能泄漏。

追问

unique_ptr 和 shared_ptr 性能差异有多大?

unique_ptr 零开销,析构直接 delete,编译后和裸指针一样。shared_ptr 的开销来自三处:一是控制块(强引用计数 + 弱引用计数)占 16 字节;二是拷贝/析构时原子操作引用计数,每次 fetch_add/fetch_sub 约 20-50ns;三是 make_shared 合并分配导致对象和控制块在同一内存块,即使所有 shared_ptr 释放了,只要还有 weak_ptr 持有控制块,对象占的内存就不会归还——这叫"对象悬挂"问题。高频场景下 shared_ptr 明显慢于 unique_ptr,但绝大多数业务代码中差异可以忽略。

什么时候用 weak_ptr?不只是循环引用?

最常见的场景确实是打破循环引用:双向链表 nextshared_ptrprevweak_ptr;观察者模式中 Subject 持有 vector<weak_ptr<Observer>>,通知时先 lock() 再调用,已销毁的 Observer 自动跳过。但 weak_ptr 还有两个实用场景:缓存系统用 weak_ptr 持有缓存对象,内存紧张时对象可以被释放,下次访问时 lock() 返回空再重新加载;工厂函数返回 weak_ptr 给调用者,工厂内部用 shared_ptr 管理生命周期,调用者通过 lock() 检查对象是否还活着。

enable_shared_from_this 是怎么回事?

当一个对象已经被 shared_ptr 管理,在成员函数里需要把自己作为 shared_ptr 传出去时,必须用 enable_shared_from_this。直接 shared_ptr<this>(this) 会创建第二个控制块,引用计数从 1 开始,导致对象被 delete 两次。典型场景:异步回调里捕获 shared_from_this() 延长对象生命周期,确保回调执行时对象还没被销毁。前提是调用 shared_from_this() 时对象已经被 shared_ptr 管理——构造函数里调会抛 bad_weak_ptr 异常。

自定义删除器怎么用?

unique_ptr 的删除器是模板参数的一部分,类型不同就不能互相赋值。shared_ptr 的删除器是运行时参数,构造时传入就行,类型相同可以放在同一容器里。实际场景:管理 FILE*fclose 作删除器、管理 POSIX 文件描述符用 close、管理 sqlite3*sqlite3_close

标签:C++