用 std::unique_ptr 替换裸指针是最直接安全的方式,因其独占所有权、自动析构且零开销;构造应优先用 make_unique,自定义删除器适配 c api,严禁管理非堆内存。

用 std::unique_ptr 替换裸指针,最直接安全
裸指针(T*)本身不带所有权语义,delete 忘了就内存泄漏,重复 delete 就崩溃。用 std::unique_ptr 是标准且零成本的封装方式——它默认独占所有权、自动调用 delete,且不增加运行时开销。
实操建议:
- 构造时用
std::make_unique<t>(...)</t>,别用new后再传给unique_ptr构造函数(避免异常安全问题) - 若需自定义释放逻辑(比如 C API 的
free或CloseHandle),传入删除器:std::unique_ptr<int void> arr(new int[10], [](int* p) { delete[] p; });</int> - 不要用
unique_ptr管理栈对象或全局对象(析构时会调用delete,UB)
什么情况下不能用 std::shared_ptr 代替裸指针
shared_ptr 带引用计数开销,且引入循环引用风险。它不是“更高级的 unique_ptr”,而是不同语义:共享所有权。裸指针转 shared_ptr 前必须确认资源生命周期确实需要被多个所有者共同延长。
常见误用场景:
- 把同一个裸指针反复传给多个
shared_ptr构造函数 → 多次析构,崩溃 - 用
shared_ptr管理非堆内存(如栈变量地址)→ 析构时调用delete,UB - 跨模块传递裸指针后,某处悄悄包成
shared_ptr,而其他地方仍用裸指针操作 → 悬空指针 + 二次释放
手动封装 RAII 类时,delete 放在析构函数里还不够
只写个析构函数调用 delete 是 RAII 的起点,但离安全还差三步:拷贝/移动语义、异常安全、资源判空。
关键检查点:
- 必须显式 = delete 拷贝构造和拷贝赋值(否则浅拷贝裸指针,两个对象析构时都
delete同一块内存) - 移动构造/赋值要置原对象指针为
nullptr,防止移动后原对象析构时误删 - 析构函数里先判空:
if (ptr_) delete ptr_;,否则多次移动后析构可能delete nullptr(虽不崩溃,但掩盖逻辑错误) - 构造函数若抛异常(比如
new失败),确保不会泄露已分配的其他资源(用make_unique可规避这类问题)
从 C 风格 API 拿到裸指针时,怎么避免过早释放
像 fopen、malloc、Windows 的 CreateFile 返回的句柄或指针,常被误认为“可以随便传给 unique_ptr”。但这些资源的释放函数往往不是 delete,且有些资源(如文件描述符)根本不是指针类型。
正确做法:
- 识别资源类型:是堆内存?文件描述符?HANDLE?OpenGL 对象 ID?每种对应不同删除器
- 用
unique_ptr时必须配对删除器,例如 Windows 文件句柄:auto deleter = [](HANDLE h) { if (h != INVALID_HANDLE_VALUE) CloseHandle(h); };<br>std::unique_ptr<:remove_pointer_t>, decltype(deleter)> file(h, deleter);</:remove_pointer_t> - 注意 C API 是否要求“必须用特定函数关闭”,比如
curl_easy_init()必须配curl_easy_cleanup(),不能用delete
裸指针封装的本质不是加个壳,而是明确所有权归属和释放契约。哪怕只改一行代码,也要同步确认谁 new、谁 delete、谁持有、谁传递——漏掉任何一环,自动释放就变成自动崩溃。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











