裸指针若仅单作用域独占、不跨函数传递所有权,可安全替换为 std::unique_ptr;需检查 new/get/delete 模式、避免 get() 长期持有、数组须用 unique_ptr。

先判断哪些裸指针能直接换成 std::unique_ptr
裸指针若只在单个作用域内拥有、不跨函数传递所有权、也不被多个模块同时持有,就符合 std::unique_ptr 的语义。这类指针通常出现在:局部动态分配(如 new Widget())、类成员中作为“专属资源”、工厂函数返回值(且调用方明确接管)。
实操建议:
- 搜索
new后立即赋值给裸指针的模式,例如Widget* w = new Widget();→ 改为auto w = std::make_unique<widget>();</widget> - 检查析构函数里是否只有
delete ptr;—— 若是,且无其他delete或reset()混用,可安全替换 - 避免对
std::unique_ptr使用get()后长期保存原始指针;若必须传参给旧接口,优先改接口,其次用ptr.get()并确保生命周期不越界 - 注意数组场景:
new T[10]必须配std::unique_ptr<t></t>,不能用std::unique_ptr<t></t>,否则析构调用delete而非delete[]
哪些地方不能硬上 std::shared_ptr,而要先理清所有权图
std::shared_ptr 不是“万能胶水”,滥用会导致循环引用、意外延长对象生命周期、甚至掩盖设计缺陷。典型误用场景包括:类成员之间互相持 shared_ptr、回调中捕获 this 但没用 weak_ptr 断环、把 shared_ptr 当参数传给只读函数却无必要。
实操建议:
- 用静态分析工具(如 Clang’s
-Wshared-ownership扩展或自定义 AST matcher)扫描疑似共享持有的裸指针赋值链,定位“谁 new、谁 delete、谁 copy、谁可能还活着” - 对观察者/监听器模式,优先用
std::weak_ptr存储回调目标,避免持有强引用;构造shared_ptr前确认该对象本身已由shared_ptr管理(即已用std::enable_shared_from_this) - 禁止在构造函数里把
this交给shared_ptr(未完成构造就计数),应改用工厂函数返回shared_ptr - 跨 DLL / SO 边界的
shared_ptr需统一使用相同 STL 版本和编译选项,否则deleter可能错位崩溃
重构时怎么绕过模板/宏/第三方库导致的编译失败
大型项目常有模板类内部用裸指针做缓存、宏封装了 new/delete、或第三方库 API 强制要求 T*。直接替换会触发大量编译错误,但逐个改底层不现实。
实操建议:
- 对模板类,先加一层适配器:例如原
Cache<t></t>持有T*,新增Cache<:unique_ptr>></:unique_ptr>版本,逐步迁移使用者 - 将宏(如
SAFE_DELETE(p))重定义为条件编译分支:#ifdef USE_SMART_PTRS下转为p.reset(),否则保留原逻辑,方便灰度切换 - 对接 C 风格 API(如
libpng、OpenGL回调)时,用std::unique_ptr管理资源,但对外暴露get();确保回调不会存储该指针——若会,必须用std::shared_ptr+ 自定义 deleter 绑定释放逻辑 - 警惕
std::auto_ptr(已弃用)残留代码,它和unique_ptr移动语义不兼容,需先全量替换为裸指针再升级
如何验证替换后没引入悬挂指针或双重释放
编译通过不等于内存安全。裸指针时代靠人工 review 很难发现 ptr 被 move 走后又被访问、或两个 shared_ptr 指向同一块 new 内存但无共同源头等问题。
实操建议:
- 开启 ASan(AddressSanitizer)+ UBSan(UndefinedBehaviorSanitizer),特别关注
use-after-free和double-delete报告;注意 ASan 对shared_ptr的引用计数操作也有一定检测能力 - 用
std::shared_ptr时,在关键节点插入if (ptr.expired()) { /* handle */ }日志,比 crash 更早暴露空悬风险 - 对复杂控制流(如异常路径、多线程共享对象),用
std::weak_ptr::lock()替代直接解引用,避免竞态下shared_ptr已销毁但 weak_ptr 还没更新 - CI 流程中加入
clang++ -fsanitize=address,undefined全量回归测试,比本地调试更能暴露边缘 case
最难的不是语法替换,而是把原本隐含在注释和约定里的所有权规则,显式编码进类型系统里。一个 shared_ptr 的引入,往往意味着要同步梳理三个以上模块间的依赖边界。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











