shared_ptr构造应优先用make_shared;避免裸指针构造以防泄漏;循环引用需用weak_ptr打破;自定义删除器须匹配类型且不抛异常;引用计数线程安全,但所指对象访问需额外同步。

shared_ptr 构造和初始化的常见误用
直接用裸指针构造 shared_ptr 是最常踩的坑——比如 new 后手动传入,一不小心就重复释放或内存泄漏。根本原因在于:shared_ptr 的引用计数只在它“真正拥有”资源时才可靠生效。
正确做法是优先用 std::make_shared:
auto p = std::make_shared<int>(42); // ✅ 推荐:一次分配控制块+对象,高效且异常安全 auto q = std::shared_ptr<int>(new int(42)); // ❌ 危险:new 可能成功但控制块分配失败,导致泄漏
- 不要用
new+ 构造函数方式,除非你明确需要自定义删除器且无法用make_shared(比如对象类型不支持默认构造) -
make_shared不支持带自定义删除器,此时必须用构造函数,并确保删除器类型匹配、生命周期可控 - 从
unique_ptr转移所有权可用shared_ptr(unique_ptr&&),但注意原unique_ptr立即为空
循环引用为什么发生,以及怎么破
两个 shared_ptr 相互持有对方管理的对象(比如父子节点、观察者-被观察者),引用计数永远不为 0,资源永不释放——这是典型的循环引用,不是 bug,是设计副作用。
核心解法是打破强引用链,用 weak_ptr 替代其中一个方向:
立即学习“C++免费学习笔记(深入)”;
struct Node {
std::shared_ptr<Node> parent;
std::vector<std::shared_ptr<Node>> children;
};
// ❌ 上面结构天然容易循环引用:parent 持有 child,child 又通过 parent 持有 parent- 把
parent改成std::weak_ptr<Node>,访问前用lock()升级为shared_ptr,失败说明父节点已析构 -
weak_ptr不增加引用计数,也不延长对象生命周期,仅作“临时观测”用途 - 别在析构函数里调用
weak_ptr::lock()—— 此时对象正被销毁,lock()必然返回空
自定义删除器的实际使用场景
默认删除器只调用 delete,但很多资源不是 new 出来的:C API 返回的句柄、malloc 分配的内存、文件描述符、GPU 显存……这时候必须指定正确的释放逻辑。
关键点在于:删除器类型是 shared_ptr 类型的一部分,不同删除器的 shared_ptr 类型不兼容:
auto fp = std::shared_ptr<FILE>(fopen("log.txt", "w"), [](FILE* f) { fclose(f); });
auto buf = std::shared_ptr<int>(static_cast<int*>(malloc(sizeof(int)*10)), [](int* p) { free(p); });- lambda 删除器会生成唯一类型,所以不能直接赋值给未指定删除器的
shared_ptr<T>变量 - 若需统一类型,用
std::function<void(T*)>作为删除器类型,但有轻微性能开销 - 删除器对象本身必须可拷贝(或移动),且不能抛异常——否则
shared_ptr析构时可能中止程序
shared_ptr 和线程安全的边界在哪
shared_ptr 的引用计数是原子操作,这意味着多个线程可同时拷贝、赋值、析构同一个 shared_ptr 实例,不会破坏计数逻辑——但仅此而已。
它**不保护**所指向对象的线程安全:
- 多个线程读写同一块
*ptr内存,仍需额外同步(如mutex或atomic) - 一个线程在拷贝
shared_ptr,另一个线程在析构它,没问题;但若一个线程在改*ptr,另一个在读,就是数据竞争 - 避免把
shared_ptr存在全局变量或长期存活容器里再跨线程传递——引用计数虽安全,但对象生命周期可能比预期长得多,掩盖真实依赖
真正难的是理清“谁该负责释放”和“谁在何时访问”,shared_ptr 只解决前者的一小部分,后者得靠设计约束和代码审查来兜底。











