在 C++11 之后,智能指针(如 std::unique_ptr、std::shared_ptr 和 std::weak_ptr)成为管理动态内存的重要工具。与手动 new/delete 相比,它们不仅能防止内存泄漏,还能简化资源共享与生命周期管理。以下从设计原则、典型用法、常见陷阱以及性能优化四个维度,系统地介绍智能指针的使用与注意事项。
1. 设计原则
- 所有权明确:
unique_ptr表示独占所有权,不能被复制;shared_ptr表示共享所有权,引用计数决定生命周期。 - 尽量避免裸指针:在函数签名、成员变量中使用裸指针往往导致所有权不清晰。
- 延迟初始化:如可能,用
std::optional<std::unique_ptr<T>>或std::unique_ptr<T[]>以防止无用的堆分配。 - 自定义 deleter:若对象需要特殊销毁逻辑(如文件句柄、网络连接),可以提供自定义 deleter,保持 RAII。
2. 典型用法
2.1 unique_ptr
std::unique_ptr <Widget> p(new Widget()); // C++14 推崇 make_unique
auto p = std::make_unique <Widget>();
// 传递所有权
void setOwner(std::unique_ptr <Widget> w) { owner = std::move(w); }
// 获取裸指针
Widget* raw = p.get(); // 仅做临时访问,不能持有
2.2 shared_ptr 与 weak_ptr
auto sp = std::make_shared <Node>();
std::weak_ptr <Node> wp = sp; // 防止循环引用
// 防止空悬
if (auto locked = wp.lock()) {
// use locked
}
2.3 动态数组
std::unique_ptr<int[]> arr(new int[10]); // 或 std::make_unique<int[]>(10)
arr[0] = 5;
3. 常见陷阱
| 场景 | 误区 | 解决方案 |
|---|---|---|
| 循环引用 | 两个对象相互持 shared_ptr |
使用 weak_ptr 断开循环 |
| 非平凡构造 | std::make_shared 需要 T 可复制 |
自定义工厂函数 |
| 多线程 | shared_ptr 线程安全 |
只读访问可安全,但写操作需同步 |
| 自定义 deleter | 需要删除多种资源 | std::unique_ptr<T, Deleter> 或 shared_ptr<T, Deleter> |
4. 性能优化
- 减少引用计数开销:如果对象只需要一次或在单线程中使用,首选
unique_ptr。 - 使用
std::make_unique/std::make_shared:一次性内存分配,减少堆碎片。 - 避免
shared_ptr的隐式转换:显式std::static_pointer_cast、std::dynamic_pointer_cast,避免多余的引用计数。 - 内联函数:在头文件中使用
inline或constexpr,让编译器更好地优化指针访问。
5. 结语
智能指针是 C++ 现代化内存管理的核心,合理使用能显著提升代码的安全性与可读性。了解其内部机制、明确所有权模型、避免常见陷阱,是成为优秀 C++ 开发者的重要一步。希望本文能为你在项目中正确使用智能指针提供实用参考。