C++11 shared_ptr 原理与详细教程
文章目录
- 一、shared_ptr
- 核心优势
- 二、shared_ptr 原理
- 2.1 引用计数机制
- 2.2 控制块结构
- 2.3 内存布局
- 2.4 简化版 shared_ptr 实现
- 2.5 关键技术点解析
- 三、shared_ptr 使用详解
- 3.1 基本用法
- 3.2 自定义删除器
- 3.3 数组支持 (C++17)
- 3.4 与 weak_ptr 配合使用
- 四、高级应用场景
- 4.1 多线程共享资源
- 4.2 作为容器元素
- 4.3 类型转换
- 五、注意事项与最佳实践
- 5.1 避免的操作
- 5.2 最佳实践
- 六、shared_ptr 与 unique_ptr 对比
- 七、总结
一、shared_ptr
std::shared_ptr
是 C++11 引入的另一种智能指针,与 unique_ptr
的独占所有权不同,它实现了共享所有权语义。多个 shared_ptr
实例可以同时管理同一个对象,当最后一个持有该对象的 shared_ptr
被销毁时,对象才会被自动释放。这种机制通过引用计数(reference counting)实现,是解决资源共享问题的理想选择。
核心优势
- 共享所有权:支持多指针共同管理同一资源
- 自动释放:最后一个所有者销毁时自动释放资源
- 灵活性:可与标准容器和算法无缝配合
- 线程安全:引用计数的修改是原子操作,支持多线程环境
- 自定义删除器:支持自定义资源释放逻辑
二、shared_ptr 原理
2.1 引用计数机制
shared_ptr
的核心是引用计数(reference count):
- 每个被管理的对象都有一个关联的引用计数器,记录当前有多少个
shared_ptr
指向它 - 当创建新的
shared_ptr
拷贝时,引用计数增加 1 - 当
shared_ptr
被销毁或重置时,引用计数减少 1 - 当引用计数变为0时,对象被自动删除
2.2 控制块结构
shared_ptr
内部通过控制块(control block)管理引用计数和其他资源。一个典型的控制块包含:
- 共享引用计数:跟踪管理对象的
shared_ptr
数量 - 弱引用计数:跟踪观察对象的
weak_ptr
数量(后续讲解) - 删除器:用于释放管理对象的函数或函数对象
- 分配器:用于内存管理的分配器
- 管理对象指针:指向实际管理的对象
2.3 内存布局
shared_ptr
通常包含两个指针大小的成员:
- 指向管理对象的指针(get() 返回的值)
- 指向控制块的指针
![shared_ptr内存布局示意图]
当使用 std::make_shared
创建 shared_ptr
时,控制块和管理对象会在同一块内存中分配,减少内存碎片并提高缓存效率。
2.4 简化版 shared_ptr 实现
下面通过实现简化版 shared_ptr
理解其工作原理:
#include <atomic>
#include <utility>// 前向声明
template <typename T>
class WeakPtr;// 控制块基类
class ControlBlockBase {
public:std::atomic<int> shared_count; // 共享引用计数std::atomic<int> weak_count; // 弱引用计数ControlBlockBase() : shared_count(1), weak_count(0) {}virtual ~ControlBlockBase() = default;virtual void destroy() = 0; // 销毁管理对象virtual void deallocate() = 0; // 释放控制块
};// 针对原始指针的控制块
template <typename T, typename Deleter>
class ControlBlock : public ControlBlockBase {
private:T* ptr; // 管理对象指针Deleter deleter; // 删除器public:ControlBlock(T* p, Deleter d) : ptr(p), deleter(std::move(d)) {}void destroy() override {deleter(ptr); // 使用删除器销毁对象}void deallocate() override {delete this; // 释放控制块自身}
};// 简化版 shared_ptr 实现
template <typename T>
class SharedPtr {
private:T* ptr; // 存储的指针ControlBlockBase* control_block; // 控制块指针// 增加引用计数void increase_count() {if (control_block) {control_block->shared_count.fetch_add(1, std::memory_order_relaxed);}}// 减少引用计数并检查是否需要销毁void decrease_count() {if (control_block) {if (control_block->shared_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {control_block->destroy(); // 销毁管理对象if (control_block->weak_count == 0) {control_block->deallocate(); // 释放控制块}}}}public:// 默认构造函数SharedPtr() : ptr(nullptr), control_block(nullptr) {}// 接受原始指针和删除器的构造函数template <typename Deleter = std::default_delete<T>>explicit SharedPtr(T* p, Deleter deleter = Deleter()) {if (p) {ptr = p;control_block = new ControlBlock<T, Deleter>(p, std::move(deleter));} else {ptr = nullptr;control_block = nullptr;}}// 拷贝构造函数SharedPtr(const SharedPtr& other) : ptr(other.ptr), control_block(other.control_block) {increase_count();}// 移动构造函数SharedPtr(SharedPtr&& other) noexcept : ptr(other.ptr), control_block(other.control_block) {other.ptr = nullptr;other.control_block = nullptr;}// 析构函数~SharedPtr() {decrease_count();}// 拷贝赋值运算符SharedPtr& operator=(const SharedPtr& other) {if (this != &other) {decrease_count(); // 减少当前引用计数ptr = other.ptr;control_block = other.control_block;increase_count(); // 增加新的引用计数}return *this;}// 移动赋值运算符SharedPtr& operator=(SharedPtr&& other) noexcept {if (this != &other) {decrease_count(); // 减少当前引用计数ptr = other.ptr;control_block = other.control_block;other.ptr = nullptr;other.control_block = nullptr;}return *this;}// 解引用运算符T& operator*() const {return *ptr;}// 成员访问运算符T* operator->() const {return ptr;}// 数组访问运算符 (C++17)T& operator[](size_t index) const {return ptr[index];}// 获取原始指针T* get() const {return ptr;}// 获取引用计数long use_count() const {if (control_block) {return control_block->shared_count.load(std::memory_order_relaxed);}return 0;}// 检查是否是唯一所有者bool unique() const {return use_count() == 1;}// 显式转换为 boolexplicit operator bool() const {return ptr != nullptr;}// 重置指针template <typename Deleter = std::default_delete<T>>void reset(T* p = nullptr, Deleter deleter = Deleter()) {SharedPtr<T>(p, std::move(deleter)).swap(*this);}// 交换指针void swap(SharedPtr& other) noexcept {std::swap(ptr, other.ptr);std::swap(control_block, other.control_block);}// 友元类声明friend class WeakPtr<T>;
};// 辅助函数:创建 shared_ptr
template <typename T, typename... Args>
SharedPtr<T> make_shared(Args&&... args) {return SharedPtr<T>(new T(std::forward<Args>(args)...));
}
2.5 关键技术点解析
-
引用计数的原子操作
- 使用
std::atomic
确保引用计数的线程安全 - 增加计数使用
memory_order_relaxed
(仅需原子性) - 减少计数使用
memory_order_acq_rel
(确保资源释放的正确顺序)
- 使用
-
控制块的多态设计
- 基类
ControlBlockBase
定义通用接口 - 派生类
ControlBlock
处理具体类型和删除器 - 支持不同类型的删除器和分配器
- 基类
-
拷贝与移动语义
- 拷贝操作增加引用计数
- 移动操作转移所有权,不修改引用计数
- 析构时减少计数,为0时销毁对象
三、shared_ptr 使用详解
3.1 基本用法
#include <memory>
#include <iostream>struct MyClass {MyClass(int id) : id(id) { std::cout << "MyClass(" << id << ") constructed\n"; }~MyClass() { std::cout << "MyClass(" << id << ") destroyed\n"; }int id;
};int main() {// 创建 shared_ptr (三种方式)auto sp1 = std::make_shared<MyClass>(1); // 推荐:高效,异常安全std::shared_ptr<MyClass> sp2(new MyClass(2)); // 不推荐:二次分配auto sp3 = std::shared_ptr<MyClass>(sp2); // 拷贝构造,引用计数变为2std::cout << "sp1 use count: " << sp1.use_count() << "\n"; // 1std::cout << "sp2 use count: " << sp2.use_count() << "\n"; // 2std::cout << "sp3 use count: " << sp3.use_count() << "\n"; // 2// 共享所有权{auto sp4 = sp2; // 新的拷贝,引用计数变为3std::cout << "Inside scope, use count: " << sp4.use_count() << "\n"; // 3} // sp4 销毁,引用计数回到2// 重置指针sp2.reset(); // sp2 不再拥有对象,引用计数变为1std::cout << "After sp2 reset, sp3 use count: " << sp3.use_count() << "\n"; // 1return 0;
} // sp1, sp3 销毁,引用计数变为0,对象被销毁
输出结果:
MyClass(1) constructed
MyClass(2) constructed
sp1 use count: 1
sp2 use count: 2
sp3 use count: 2
Inside scope, use count: 3
After sp2 reset, sp3 use count: 1
MyClass(1) destroyed
MyClass(2) destroyed
3.2 自定义删除器
shared_ptr
支持自定义删除器,用于非默认的资源释放逻辑:
#include <cstdio>// 自定义文件删除器
struct FileDeleter {void operator()(FILE* fp) const {if (fp) {std::fclose(fp);std::cout << "File closed\n";}}
};int main() {// 使用自定义删除器管理文件指针std::shared_ptr<FILE> file_ptr(std::fopen("example.txt", "w"), FileDeleter());if (file_ptr) {std::fputs("Hello, shared_ptr!", file_ptr.get());}// 离开作用域时自动调用 FileDeleter 关闭文件return 0;
}
也可使用 lambda 表达式作为删除器:
auto deleter = [](MyClass* p) {std::cout << "Custom deleter for MyClass(" << p->id << ")\n";delete p;
};std::shared_ptr<MyClass> sp(new MyClass(3), deleter);
3.3 数组支持 (C++17)
C++17 开始支持管理动态数组:
// C++17 数组支持
auto arr_ptr = std::make_shared<int[]>(5); // 创建包含5个int的数组// 访问数组元素
for (int i = 0; i < 5; ++i) {arr_ptr[i] = i * 10;
}// 数组版本会自动使用 delete[] 释放资源
3.4 与 weak_ptr 配合使用
shared_ptr
的最大问题是可能产生循环引用,此时需要 weak_ptr
打破循环:
#include <memory>struct Node {int data;std::shared_ptr<Node> next; // 共享指针导致循环引用// std::weak_ptr<Node> next; // 使用弱指针打破循环
};int main() {auto node1 = std::make_shared<Node>(1);auto node2 = std::make_shared<Node>(2);node1->next = node2;node2->next = node1; // 形成循环引用// 此时 node1 和 node2 的引用计数都是2,离开作用域后不会减为0// 导致内存泄漏!return 0;
}
解决方法是将其中一个 shared_ptr
改为 weak_ptr
:
struct Node {int data;std::weak_ptr<Node> next; // 弱指针不增加引用计数
};
weak_ptr
特性:
- 不拥有对象所有权,不增加引用计数
- 可通过
lock()
方法获取shared_ptr
(如果对象存在) - 用于解决循环引用问题
- 可通过
expired()
检查对象是否已被销毁
四、高级应用场景
4.1 多线程共享资源
shared_ptr
的引用计数操作是线程安全的,可以安全地在多线程间传递:
#include <thread>
#include <vector>void func(std::shared_ptr<MyClass> sp) {// 线程安全:引用计数的增减是原子操作std::this_thread::sleep_for(std::chrono::milliseconds(10));std::cout << "Thread " << std::this_thread::get_id() << ", use count: " << sp.use_count() << "\n";
}int main() {auto sp = std::make_shared<MyClass>(1);std::vector<std::thread> threads;// 创建5个线程共享 spfor (int i = 0; i < 5; ++i) {threads.emplace_back(func, sp);}for (auto& t : threads) {t.join();}std::cout << "Main thread, use count: " << sp.use_count() << "\n";return 0;
}
4.2 作为容器元素
shared_ptr
可安全存储在标准容器中:
#include <vector>
#include <algorithm>int main() {std::vector<std::shared_ptr<MyClass>> objects;// 添加元素objects.push_back(std::make_shared<MyClass>(1));objects.push_back(std::make_shared<MyClass>(2));objects.push_back(std::make_shared<MyClass>(3));// 遍历并访问元素std::for_each(objects.begin(), objects.end(), [](const auto& sp) {std::cout << "MyClass id: " << sp->id << "\n";});return 0;
}
4.3 类型转换
shared_ptr
提供专门的类型转换函数,类似于 C++ 的强制类型转换:
struct Base { virtual ~Base() = default; };
struct Derived : Base { int value = 42; };int main() {std::shared_ptr<Base> base_ptr = std::make_shared<Derived>();// 动态转换(安全检查)if (auto derived_ptr = std::dynamic_pointer_cast<Derived>(base_ptr)) {std::cout << "Derived value: " << derived_ptr->value << "\n";}// 静态转换(无安全检查)auto derived_ptr = std::static_pointer_cast<Derived>(base_ptr);std::cout << "Derived value: " << derived_ptr->value << "\n";return 0;
}
五、注意事项与最佳实践
5.1 避免的操作
-
不要将原始指针交给多个 shared_ptr
// 错误示例:多个 shared_ptr 独立管理同一资源 int* raw_ptr = new int(10); std::shared_ptr<int> sp1(raw_ptr); std::shared_ptr<int> sp2(raw_ptr); // 严重错误!两个独立控制块,导致双重释放
-
避免循环引用
// 循环引用导致内存泄漏 struct A { std::shared_ptr<B> b; }; struct B { std::shared_ptr<A> a; };auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->b = b; b->a = a; // 循环引用,引用计数永远不为0
解决:将其中一个改为
std::weak_ptr
-
不要用 shared_ptr 管理栈上对象
// 错误示例:管理栈上对象 int x = 10; std::shared_ptr<int> sp(&x); // 析构时会调用 delete &x,导致未定义行为
5.2 最佳实践
-
优先使用 std::make_shared
- 优点1:一次分配内存(控制块+对象),效率更高
- 优点2:异常安全,避免资源泄漏
auto sp1 = std::make_shared<MyClass>(); // 推荐 auto sp2 = std::shared_ptr<MyClass>(new MyClass()); // 不推荐
-
避免使用 get() 返回的原始指针
- 不要将 get() 返回的指针交给另一个智能指针
- 不要手动释放 get() 返回的指针
-
正确处理数组(C++17前)
- C++17 前
shared_ptr
不直接支持数组,需提供自定义删除器
// C++17 前管理数组的正确方式 std::shared_ptr<int> arr_ptr(new int[5], std::default_delete<int[]>());
- C++17 前
-
使用 weak_ptr 解决循环引用
- 在双向链表、树等数据结构中,父节点持有子节点的
shared_ptr
,子节点持有父节点的weak_ptr
- 在双向链表、树等数据结构中,父节点持有子节点的
-
线程安全注意事项
shared_ptr
本身的引用计数操作是线程安全的- 但管理的对象不是线程安全的,仍需同步机制保护
六、shared_ptr 与 unique_ptr 对比
特性 | shared_ptr | unique_ptr |
---|---|---|
所有权 | 共享所有权 | 独占所有权 |
大小 | 两个指针大小 | 一个指针大小 |
引用计数 | 有 | 无 |
拷贝操作 | 允许(增加计数) | 禁止 |
移动操作 | 允许 | 允许 |
循环引用 | 可能(需 weak_ptr 解决) | 不可能 |
数组支持 | C++17 开始支持 | 原生支持 |
性能 | 引用计数操作有开销 | 无额外开销 |
选择建议:
- 当需要共享资源时,使用
shared_ptr
- 当资源仅需独占时,使用
unique_ptr
(性能更优) - 优先考虑
unique_ptr
,仅在必要时才使用shared_ptr
七、总结
std::shared_ptr
是 C++ 中实现共享所有权的智能指针,通过引用计数机制允许多个指针共同管理同一资源。其核心特点包括:
- 共享所有权:多个
shared_ptr
可指向同一对象 - 自动释放:最后一个所有者销毁时释放资源
- 线程安全:引用计数操作是原子的,支持多线程环境
- 灵活性:支持自定义删除器和分配器
- 配合 weak_ptr:可解决循环引用问题
在实际开发中,应根据资源所有权需求选择合适的智能指针,优先考虑 unique_ptr
以获得更好的性能,仅在需要共享资源时使用 shared_ptr
。