015 C++的shared_ptr
作者Lou Xiao, deepseek创建时间2025-04-02 14:16:16更新时间2025-04-02 14:16:16
1. 基本概念
1.1 智能指针概述
- 智能指针是C++ RAII(资源获取即初始化)机制的典型应用
- 自动管理动态分配的内存,防止内存泄漏
- 标准库提供的主要智能指针:
std::shared_ptr
,std::unique_ptr
,std::weak_ptr
1.2 std::shared_ptr
定义
- 共享所有权的智能指针
- 多个
shared_ptr
可以指向同一个对象 - 通过引用计数机制管理对象的生命周期
- 当最后一个
shared_ptr
离开作用域时,自动销毁管理的对象
2. 核心特性
2.1 引用计数机制
- 每个
shared_ptr
管理的对象都有一个关联的引用计数器 - 当
shared_ptr
被拷贝时,引用计数增加 - 当
shared_ptr
被销毁或重置时,引用计数减少 - 引用计数为0时,自动删除管理的对象
2.2 线程安全性
- 引用计数的修改是原子操作,线程安全
- 但管理的对象本身不提供线程安全保证
- 多个线程同时访问同一个对象仍需同步机制
2.3 自定义删除器
- 可以指定自定义的删除器代替
delete
- 适用于管理非内存资源或需要特殊清理的对象
- 语法:
std::shared_ptr<T> ptr(pointer, deleter)
3. 基本用法
3.1 创建shared_ptr
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
// 方式1:直接构造
2
std::shared_ptr<int> p1(new int(42));
3
4
// 方式2:使用make_shared (推荐)
5
std::shared_ptr<int> p2 = std::make_shared<int>(42);
6
7
// 方式3:拷贝构造
8
std::shared_ptr<int> p3 = p2;
9
10
// 方式4:自定义删除器
11
std::shared_ptr<FILE> filePtr(fopen("test.txt", "r"), [](FILE* f) {
12
if(f) fclose(f);
13
});
3.2 常用操作
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
// 解引用
2
*p1 = 10;
3
4
// 访问成员
5
std::shared_ptr<std::string> strPtr = std::make_shared<std::string>("hello");
6
strPtr->size();
7
8
// 获取原始指针
9
int* rawPtr = p1.get();
10
11
// 重置指针
12
p1.reset(); // 释放当前对象,变为空指针
13
p1.reset(new int(100)); // 指向新对象
14
15
// 检查是否唯一所有者
16
if(p1.unique()) { /* ... */ }
17
18
// 交换两个shared_ptr
19
std::shared_ptr<int> p4 = std::make_shared<int>(50);
20
p1.swap(p4);
4. 高级特性
4.1 std::make_shared
的优势
- 一次性分配内存,效率更高
- 减少内存碎片
- 异常安全
- 语法:
auto ptr = std::make_shared<T>(args...)
4.2 类型转换
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
struct Base { virtual ~Base() = default; };
2
struct Derived : public Base {};
3
4
std::shared_ptr<Derived> dptr = std::make_shared<Derived>();
5
6
// 静态转换
7
std::shared_ptr<Base> bptr = std::static_pointer_cast<Base>(dptr);
8
9
// 动态转换
10
std::shared_ptr<Derived> dptr2 = std::dynamic_pointer_cast<Derived>(bptr);
11
12
// 常量转换
13
std::shared_ptr<const Base> cptr = std::const_pointer_cast<const Base>(bptr);
4.3 循环引用问题
- 当两个或多个
shared_ptr
互相引用时会导致内存泄漏 - 解决方案:使用
std::weak_ptr
打破循环
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
struct Node {
2
std::shared_ptr<Node> next;
3
std::weak_ptr<Node> prev; // 使用weak_ptr避免循环引用
4
};
5. 性能考虑
5.1 开销分析
- 内存开销:每个
shared_ptr
需要存储控制块(引用计数等) - 时间开销:引用计数的原子操作
- 对比
unique_ptr
:shared_ptr
开销更大
5.2 使用场景
- 适合使用
shared_ptr
的情况: - 多个对象需要共享所有权
- 对象生命周期不明确
- 需要将指针存入容器
- 不适合的情况:
- 单一所有权场景(应使用
unique_ptr
) - 性能关键路径
- 需要明确生命周期管理的场景
6. 最佳实践
- 优先使用
make_shared
而非直接new
- 避免原始指针和
shared_ptr
混用 - 不推荐使用
get()
获取的原始指针创建新的shared_ptr
- 在可能产生循环引用的地方使用
weak_ptr
- 在接口中明确所有权语义
- 避免不必要的
shared_ptr
拷贝 - 考虑使用
std::enable_shared_from_this
从this
创建shared_ptr
7. 常见陷阱
- 原始指针混用
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
int* raw = new int(10);
2
std::shared_ptr<int> p1(raw);
3
std::shared_ptr<int> p2(raw); // 错误!会导致双重释放
- 循环引用导致内存泄漏
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
struct A {
2
std::shared_ptr<B> b;
3
};
4
struct B {
5
std::shared_ptr<A> a;
6
};
7
8
auto a = std::make_shared<A>();
9
auto b = std::make_shared<B>();
10
a->b = b;
11
b->a = a; // 循环引用,内存泄漏
- 从
this
创建shared_ptr
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
struct Bad {
2
std::shared_ptr<Bad> getptr() {
3
return std::shared_ptr<Bad>(this); // 错误!
4
}
5
};
6
// 正确做法:继承std::enable_shared_from_this
- 数组误用
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
// 错误:shared_ptr默认使用delete而非delete[]
2
std::shared_ptr<int> arr(new int[10]);
3
4
// 正确:提供数组删除器
5
std::shared_ptr<int> arr(new int[10], [](int* p) { delete[] p; });
6
// 或使用C++17的shared_ptr<T[]>
8. C++17/20增强
8.1 C++17新特性
shared_ptr
支持数组类型:std::shared_ptr<T[]>
std::reinterpret_pointer_cast
std::make_shared
支持数组
8.2 C++20新特性
std::atomic<std::shared_ptr>
(并发TS)std::shared_ptr
与std::weak_ptr
的原子操作
9. 与其他智能指针比较
特性 | shared_ptr | unique_ptr | weak_ptr |
---|---|---|---|
所有权 | 共享 | 独占 | 无 |
引用计数 | 是 | 否 | 是 |
可复制 | 是 | 否 | 是 |
可移动 | 是 | 是 | 是 |
管理数组 | C++17支持 | 支持 | 不直接支持 |
循环引用解决方案 | 需配合weak_ptr | 无此问题 | 专门解决 |
10. 总结
std::shared_ptr
是C++中实现共享所有权的核心工具,通过引用计数自动管理对象生命周期。正确使用时能极大简化内存管理,但需要注意循环引用、性能开销等问题。在设计中应明确所有权语义,优先考虑unique_ptr
,仅在需要共享所有权时使用shared_ptr
。
附录: std::shared_ptr
的常用 API 表格
名称 | 用途 | 原型 | 代码示例 | 说明 |
---|---|---|---|---|
构造函数 | 创建 shared_ptr | shared_ptr(); shared_ptr(T* ptr); shared_ptr(nullptr_t); | std::shared_ptr<int> p1; std::shared_ptr<int> p2(new int(42)); | 默认构造或从裸指针构造。注意避免直接传递 new 的结果(推荐 make_shared )。 |
make_shared | 安全创建 shared_ptr | template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args); | auto p = std::make_shared<int>(42); | 一次性分配内存和控制块,避免内存泄漏风险。 |
析构函数 | 释放资源 | ~shared_ptr(); | 自动调用 | 引用计数减 1,计数为 0 时销毁对象。 |
operator= | 赋值 | shared_ptr& operator=(const shared_ptr& r); | p1 = p2; | 共享所有权,引用计数更新。 |
reset | 重置指针 | void reset(); void reset(T* ptr); | p.reset(); p.reset(new int(10)); | 释放当前所有权,可替换为新指针(或置空)。 |
get | 获取裸指针 | T* get() const; | int* raw = p.get(); | 不改变引用计数,谨慎使用以避免悬垂指针。 |
operator* | 解引用 | T& operator*() const; | int val = *p; | 访问托管对象。 |
operator-> | 成员访问 | T* operator->() const; | p->method(); | 访问托管对象的成员。 |
use_count | 获取引用计数 | long use_count() const; | cout << p.use_count(); | 主要用于调试,性能可能不高。 |
unique | 检查是否独占所有权 | bool unique() const; | if (p.unique()) { ... } | 等价于 use_count() == 1 (C++17 后弃用)。 |
swap | 交换指针 | void swap(shared_ptr& r); | p1.swap(p2); | 高效交换两个 shared_ptr 的所有权。 |
owner_before | 提供弱序比较 | template<class U> bool owner_before(const shared_ptr<U>& other) const; | if (p1.owner_before(p2)) { ... } | 用于关联容器中的排序,不比较对象值。 |
别名构造函数 | 共享对象但指向其子部分 | shared_ptr(const shared_ptr<U>& r, T* ptr); | std::shared_ptr<int> p_alias(p, &p->data); | 共享原指针的引用计数,但指向不同地址。 |
自定义删除器 | 指定自定义资源释放逻辑 | shared_ptr(T* ptr, Deleter d); | std::shared_ptr<FILE> fp(fopen("a.txt", "r"), fclose); | 删除器在引用计数为 0 时调用。 |
类型转换 | 动态/静态指针转换 | template<class T, class U> shared_ptr<T> dynamic_pointer_cast(const shared_ptr<U>& r); | auto derived = std::dynamic_pointer_cast<Derived>(base); | 类似 dynamic_cast ,但返回 shared_ptr 。 |
关键注意事项:
- 避免循环引用:可能导致内存泄漏,需配合
std::weak_ptr
使用。 - 线程安全:引用计数操作是原子的,但对象访问需额外同步。
- 性能:控制块分配可能带来开销,优先使用
make_shared
。
如果需要更详细的特定场景说明或扩展 API(如 atomic_shared_ptr
),可以进一步补充。
文章目录