shared_ptr
是 C++ 标准库(<memory>
头文件)中提供的一种智能指针,用于管理动态分配的内存。它通过引用计数(reference counting)来实现多个 shared_ptr
对象共享同一块内存的所有权。当最后一个 shared_ptr
被销毁时,内存会被自动释放。
一、创建 shared_ptr
的几种方法
1. 使用 std::make_shared
(推荐)
#include <memory>
#include <iostream>
int main() {
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::cout << *sp << std::endl; // 输出 42
return 0;
}
- 优点:效率高,一次性分配对象和控制块的内存。
- 适用场景:大多数情况下推荐使用。
2. 通过构造函数和原始指针
std::shared_ptr<int> sp(new int(42));
- 特点:显式地将原始指针交给
shared_ptr
管理。 - 注意:避免用同一原始指针初始化多个
shared_ptr
,否则会导致双重释放(详见后文)。
3. 通过拷贝或赋值
std::shared_ptr<int> sp1 = std::make_shared<int>(42);
std::shared_ptr<int> sp2 = sp1; // 共享所有权,引用计数增 1
- 特点:多个
shared_ptr
共享同一块内存,引用计数自动管理。
二、shared_ptr
的常见操作
1. 基本方法
- 获取引用计数:
use_count()
返回当前共享该资源的shared_ptr
数量。std::shared_ptr<int> sp1 = std::make_shared<int>(42); std::shared_ptr<int> sp2 = sp1; std::cout << sp1.use_count() << std::endl; // 输出 2
- 检查是否为空:
if (!sp)
或sp.get() == nullptr
。 - 重置:
reset()
清空或重新绑定。sp2.reset(); // 做了两步:将指向对象的指针设置为nullptr,引用计数减一 sp2.reset(new int(40)); // 将指针指向新对象
2. 引用方法
- 解引用:使用
*
访问对象。std::cout << *sp1 << std::endl; // 输出 42
- 成员访问:对于类对象,使用
->
。struct MyClass { int value = 10; }; std::shared_ptr<MyClass> sp = std::make_shared<MyClass>(); std::cout << sp->value << std::endl; // 输出 10
- 获取原始指针:
get()
返回底层指针(但不建议直接操作)。
三、常见应用场景
-
动态资源管理:
- 替代手动
new
和delete
,确保内存自动释放。std::shared_ptr<int> sp = std::make_shared<int>(42); // 离开作用域时自动释放
- 替代手动
-
函数参数传递
- 避免拷贝开销,同时确保资源安全管理。
void process(std::shared_ptr<int> ptr) { std::cout << *ptr << std::endl; }
- 避免拷贝开销,同时确保资源安全管理。
-
工厂函数:
- 返回动态分配的对象时,使用
shared_ptr
传递所有权。std::shared_ptr<int> create_resource() { return std::make_shared<int>(100); }
- 返回动态分配的对象时,使用
-
管理第三方库资源
- 使用自定义删除器管理非标准内存(如文件句柄、第三方库指针)。
std::shared_ptr<FILE> file( fopen("test.txt", "r"), [](FILE* f) { if (f) fclose(f); } );
- 使用自定义删除器管理非标准内存(如文件句柄、第三方库指针)。
四、使用注意事项
1. 双重释放问题 (Double Deletion)
问题描述
当用同一原始指针初始化多个独立的 shared_ptr
时,每个 shared_ptr
析构时都会尝试释放内存,导致未定义行为。(每个shared_ptr是独立的,引用计数器也是独立的,引用计数为0时,都尝试释放对象,从而导致双重释放的问题)
示例:
int* raw = new int(42);
std::shared_ptr<int> sp1(raw);
std::shared_ptr<int> sp2(raw); // 错误!双重释放
解决方法:
使用 make_shared
或通过拷贝共享:
std::shared_ptr<int> sp1 = std::make_shared<int>(42);
std::shared_ptr<int> sp2 = sp1; // 正确
2. 循环引用问题 (Circular Reference)
问题描述
当对象间通过 shared_ptr
互相引用时,引用计数无法降到 0,导致内存泄漏。(每个节点的引用计数都是2,超出作用域时,调用析构函数,引用计数减一,但无法变为0,导致内存无法释放)
示例
class Node {
public:
std::shared_ptr<Node> next;
~Node() { std::cout << "Destructor" << std::endl; }
};
int main() {
std::shared_ptr<Node> node1 = std::make_shared<Node>();
std::shared_ptr<Node> node2 = std::make_shared<Node>();
node1->next = node2;
node2->next = node1; // 循环引用
return 0; // 内存泄漏
}
解决方法
- 使用
std::weak_ptr
打破循环(引用计数都是1,超出作用域后,调用析构函数,引用计数变为0,释放内存)class Node { public: std::weak_ptr<Node> next; // 改为 weak_ptr ~Node() { std::cout << "Destructor" << std::endl; } };
3. 避免与原始指针混用
不要在 shared_ptr
之外直接操作原始指针(如通过 get()
获取后手动 delete
),会导致未定义行为。
五、结语
shared_ptr
是 C++ 中管理动态内存的利器,通过引用计数实现资源共享和自动释放。掌握其创建方法和常见操作,能显著提升代码的安全性与可维护性。然而双重释放、循环引用等问题需要特别注意。通过合理使用 make_shared
、weak_ptr
,可以有效规避这些陷阱。