在现代 C++ 开发中,智能指针已成为内存管理的核心工具。相比手动使用 new / delete,智能指针可以自动管理资源,防止内存泄漏和悬空指针。本文将聚焦 std::shared_ptr,从内部实现原理到实际使用技巧,帮助你在项目中更安全、高效地使用共享所有权。
1. 什么是 std::shared_ptr
std::shared_ptr 是一种引用计数式智能指针,它允许多个指针实例共享同一块资源,并在最后一个指针销毁时自动释放内存。核心特性:
- 引用计数:记录有多少
shared_ptr指向同一对象。 - 线程安全:计数器自增/自减操作采用原子操作。
- 自定义删除器:可以为不同类型的资源指定不同的销毁方式。
2. 内部实现细节
2.1 控制块(Control Block)
每个 shared_ptr 实例实际上引用了一个 控制块(control_block),该块包含:
| 成员 | 作用 |
|---|---|
| `std::atomic | |
| use_count` | 活跃引用计数 |
| `std::atomic | |
weak_count| 弱引用计数(与weak_ptr` 相关) |
|
T* ptr |
实际指向的对象 |
Deleter deleter |
自定义删除器 |
控制块在第一个 shared_ptr 创建时分配,并在 use_count 归零后,先销毁对象再释放自身。
2.2 内存布局
struct ControlBlock {
std::atomic <size_t> use_count{1};
std::atomic <size_t> weak_count{0};
T* ptr;
Deleter deleter;
};
当 use_count 变为 0 时:
- 调用
deleter(ptr)释放对象。 - 若
weak_count也为 0,则delete this释放控制块。
2.3 线程安全实现
bool use_count_dec() {
return use_count.fetch_sub(1, std::memory_order_acq_rel) == 1;
}
使用 acq_rel(获取/释放)保证在多线程中对计数器的正确顺序。
3. 常见使用场景
3.1 共享资源
std::shared_ptr <Worker> p1 = std::make_shared<Worker>();
std::shared_ptr <Worker> p2 = p1; // 引用计数 +1
3.2 循环引用解决方案
class A;
class B {
public:
std::shared_ptr <A> a; // 如果 A 也持有 B 的 shared_ptr,导致循环引用
};
class A {
public:
std::weak_ptr <B> b; // 用 weak_ptr 断开循环
};
3.3 自定义删除器
auto deleter = [](int* p){ std::cout << "freeing\n"; delete p; };
std::shared_ptr <int> sp(new int(5), deleter);
3.4 与 C API 交互
std::shared_ptr <FILE> file(fopen("log.txt", "r"), fclose);
4. 性能注意事项
- 控制块分配:
make_shared在一次内存分配中同时创建对象和控制块,减少碎片。 - 弱引用计数:如果不需要
weak_ptr,不必使用shared_ptr,否则weak_count仍会占用额外内存。 - 避免大量临时:
shared_ptr复制代价较大,尽量传递引用(`const std::shared_ptr &`)或使用 `std::move`。
5. 常见陷阱与解决方案
| 陷阱 | 说明 | 解决方案 |
|---|---|---|
| 悬空指针 | shared_ptr 复制后,旧指针仍然有效 |
reset() 立即释放,或使用 unique_ptr |
| 资源泄漏 | 循环引用导致计数 never zero | 使用 weak_ptr 解除循环 |
| 多线程 race | use_count 变更不原子 |
依赖标准库实现,或使用 atomic 计数器 |
6. 代码示例:实现一个简单的图形系统
#include <memory>
#include <iostream>
#include <vector>
struct Shape {
virtual void draw() const = 0;
virtual ~Shape() { std::cout << "Shape destroyed\n"; }
};
struct Circle : Shape {
void draw() const override { std::cout << "Circle\n"; }
~Circle() { std::cout << "Circle destroyed\n"; }
};
struct Group {
std::vector<std::shared_ptr<Shape>> members;
void add(const std::shared_ptr <Shape>& s){ members.push_back(s); }
void drawAll() const { for(auto& m: members) m->draw(); }
~Group() { std::cout << "Group destroyed\n"; }
};
int main() {
auto circle1 = std::make_shared <Circle>();
auto circle2 = std::make_shared <Circle>();
auto group = std::make_shared <Group>();
group->add(circle1);
group->add(circle2);
group->drawAll(); // Circle Circle
// 共享圆形
circle1->draw(); // Circle
return 0;
}
运行结束后,控制台会输出:
Circle
Circle
Group destroyed
Circle destroyed
Circle destroyed
Shape destroyed
显示了 shared_ptr 的引用计数机制:只有在所有 shared_ptr 失效后才销毁对象。
7. 小结
std::shared_ptr是 C++11 之后安全、方便的共享所有权实现。- 内部通过控制块实现引用计数,线程安全且支持自定义删除器。
- 正确使用
weak_ptr可以避免循环引用,保证资源及时释放。 - 在性能敏感代码中,优先使用
make_shared并减少临时对象。
掌握这些细节后,你可以在复杂项目中更放心地使用 shared_ptr,让内存管理变得透明、可维护。祝编码愉快!