std::shared_ptr 是 C++ 中用于共享所有权的智能指针,通过引用计数机制管理动态资源。以下是其核心特性、使用场景及注意事项的详细说明:
1. 核心特性
| 特性 | 说明 |
|---|---|
| 共享所有权 | 多个 shared_ptr 可指向同一对象,引用计数归零时自动释放资源 |
| 原子引用计数 | 引用计数的增减是线程安全的(但指向的对象本身需用户保证线程安全) |
| 自定义删除器 | 支持自定义资源释放逻辑(如文件句柄、网络连接) |
| 内存高效 | std::make_shared 将对象和控制块合并分配,减少内存碎片 |
2. 基础用法
创建与初始化
- 推荐方式(异常安全且高效):
auto sp1 = std::make_shared<Widget>(); // 创建 Widget 对象 auto sp2 = std::make_shared<int[]>(10); // C++20 起支持动态数组 - 传统方式(避免使用裸指针直接初始化):
std::shared_ptr<Widget> sp3(new Widget()); // 潜在风险:若后续代码抛出异常可能泄漏
共享所有权
auto sp4 = sp1; // 引用计数 +1
auto sp5 = std::move(sp1); // 移动语义,sp1 变为 nullptr,计数不变
资源释放
sp2.reset(); // 主动释放资源,引用计数 -1
sp4 = nullptr; // 等价于 reset()
3. 高级用法
自定义删除器
// 示例:管理文件句柄
std::shared_ptr<FILE> file(
fopen("data.txt", "r"),
[](FILE* f) {
if (f) {
fclose(f);
std::cout << "文件已关闭\n";
}
}
);
别名构造(Alias Constructor)
class Base { /* ... */ };
class Derived : public Base { /* ... */ };
auto derived = std::make_shared<Derived>();
std::shared_ptr<Base> base(derived, derived.get()); // 共享引用计数,但指向基类部分
弱引用与循环引用解决
class Parent;
class Child {
public:
std::shared_ptr<Parent> parent; // 错误:导致循环引用
// 修正方案:
std::weak_ptr<Parent> parent; // 弱引用不增加计数
};
4. 性能与线程安全
| 场景 | 性能影响 | 解决方案 |
|---|---|---|
| 高频创建/销毁 | 原子操作引用计数可能成为瓶颈(尤其在多线程环境) | 改用 unique_ptr 或对象池 |
| 跨线程传递 | 引用计数线程安全,但对象本身需同步 | 使用互斥锁保护对象数据 |
| 控制块分离 | 若通过 new 初始化,对象和控制块分离,可能引发缓存不命中 | 优先使用 std::make_shared |
5. 常见错误与规避
错误 1:同一裸指针初始化多个shared_ptr
Widget* raw = new Widget();
std::shared_ptr<Widget> sp6(raw);
// std::shared_ptr<Widget> sp7(raw); // 错误!会导致重复释放
修正:始终通过已存在的 shared_ptr 复制,或使用 make_shared。
错误 2:忽略循环引用
struct Node {
std::shared_ptr<Node> next; // 循环引用导致内存泄漏
};
修正:将至少一个指针改为 weak_ptr。
错误 3:误用get()返回的裸指针
auto sp8 = std::make_shared<int>(42);
int* raw_ptr = sp8.get();
delete raw_ptr; // 错误!sp8 析构时会再次释放
修正:禁止手动释放由 shared_ptr 管理的资源。
6. 最佳实践
-
优先使用
std::make_shared- 合并内存分配,提升性能。
- 保证异常安全(避免
new和shared_ptr构造之间的异常)。
-
明确所有权语义
- 仅在需要共享所有权时使用
shared_ptr,否则优先用unique_ptr。
- 仅在需要共享所有权时使用
-
跨模块边界谨慎传递
- 动态库接口中避免直接传递
shared_ptr(不同模块可能使用不同堆管理器)。
- 动态库接口中避免直接传递
-
性能敏感场景优化
// 局部非原子计数优化(C++20 引入) std::shared_ptr<Widget> sp9 = std::make_shared<Widget>(); std::shared_ptr<Widget> sp10 = std::shared_ptr(sp9); // 非原子递增(若仅在单线程使用)
示例:线程安全的对象缓存
#include <memory>
#include <mutex>
#include <unordered_map>
class Cache {
std::mutex mtx;
std::unordered_map<int, std::weak_ptr<Resource>> cache;
public:
std::shared_ptr<Resource> get(int key) {
std::lock_guard<std::mutex> lock(mtx);
auto it = cache.find(key);
if (it != cache.end()) {
auto sp = it->second.lock(); // 尝试提升为 shared_ptr
if (sp) return sp;
}
auto new_sp = std::make_shared<Resource>(key);
cache[key] = new_sp; // 存储 weak_ptr
return new_sp;
}
};
总结
std::shared_ptr 是管理共享资源的利器,但需注意:
- 优先使用
make_shared保证安全和性能 - 用
weak_ptr打破循环引用 - 避免与裸指针混用导致重复释放
- 多线程中需额外保护共享对象本身
正确使用时,可显著降低内存泄漏和悬空指针风险,提升代码健壮性。
5463

被折叠的 条评论
为什么被折叠?



