为什么需要智能指针?
在C++开发中,手动管理内存资源(如new
/delete
)容易导致:
- 内存泄漏:忘记释放资源
- 悬垂指针:访问已释放的内存
- 双重释放:多次删除同一对象
智能指针通过RAII(Resource Acquisition Is Initialization)技术自动管理资源生命周期:
- 在构造函数中获取资源
- 在析构函数中释放资源
- 确保异常安全(即使发生异常也能正确释放资源)
1 2 3 4 5 6 7 8 9 10 11 12
| void unsafe_example() { int* raw_ptr = new int(42); delete raw_ptr; }
void safe_example() { std::unique_ptr<int> smart_ptr = std::make_unique<int>(42); }
|
三大智能指针使用场景
1. unique_ptr:独占所有权指针
- 适用场景:
- 工厂函数返回对象
- 作为类成员(明确所有权关系)
- 局部临时对象管理
1 2 3 4 5 6 7 8 9 10 11 12 13
| std::unique_ptr<Connection> create_connection() { return std::make_unique<Connection>("db://localhost"); }
class DeviceController { private: std::unique_ptr<Device> device_; public: DeviceController() : device_(std::make_unique<Device>()) {} };
|
2. shared_ptr:共享所有权指针
- 适用场景:
- 多对象共享同一资源
- 缓存系统
- 观察者模式(需配合weak_ptr)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class AppConfig { public: static std::shared_ptr<AppConfig> load() { static auto config = std::make_shared<AppConfig>(); return config; } };
void module1() { auto config = AppConfig::load(); }
void module2() { auto config = AppConfig::load(); }
|
3. weak_ptr:解决循环引用问题
- 适用场景:
- 打破shared_ptr循环引用
- 实现缓存和观察者模式
- 临时访问共享资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Observer { std::weak_ptr<Subject> subject_; public: void observe(std::shared_ptr<Subject> subject) { subject_ = subject; } void notify() { if (auto subject = subject_.lock()) { subject->update(); } } };
|
智能指针最佳实践
1. 优先使用make_unique/make_shared
1 2 3 4 5
| auto ptr1 = std::make_shared<Widget>();
std::shared_ptr<Widget> ptr2(new Widget);
|
2. 避免裸指针转换
1 2 3 4 5 6 7 8
| Widget* raw = new Widget; std::shared_ptr<Widget> p1(raw); std::shared_ptr<Widget> p2(raw);
auto p1 = std::make_shared<Widget>(); auto p2 = p1;
|
3. 自定义删除器处理特殊资源
1 2 3 4 5 6 7
| std::unique_ptr<FILE, decltype(&fclose)> file_ptr(fopen("data.txt", "r"), &fclose);
std::shared_ptr<int[]> arr(new int[10], [](int* p) { delete[] p; });
|
4. 多线程注意事项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| std::shared_ptr<Counter> global_counter;
void thread_work() { auto local_counter = global_counter; if (!local_counter) { std::lock_guard<std::mutex> lock(mtx); if (!global_counter) { global_counter = std::make_shared<Counter>(); } local_counter = global_counter; } }
|
常见陷阱与调试技巧
陷阱1:循环引用
1 2 3 4 5 6 7 8 9 10
| struct Node { std::shared_ptr<Node> next; std::shared_ptr<Node> prev; };
auto node1 = std::make_shared<Node>(); auto node2 = std::make_shared<Node>(); node1->next = node2; node2->prev = node1;
|
解决方案:使用weak_ptr打破循环
陷阱2:返回unique_ptr的原始指针
1 2 3 4 5 6 7
| class ResourceHolder { std::unique_ptr<Resource> resource_; public: Resource* get() const { return resource_.get(); } };
|
解决方案:返回weak_ptr或const引用
调试技巧:使用enable_shared_from_this
1 2 3 4 5 6 7 8
| class Session : public std::enable_shared_from_this<Session> { public: void process() { auto self = shared_from_this(); queue_.push(self); } };
|
C++17/20智能指针增强
C++17特性
1 2 3 4 5 6 7 8
| std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);
auto [ptr1, ptr2] = std::make_tuple( std::make_unique<int>(1), std::make_unique<int>(2) );
|
C++20特性
1 2 3 4 5
| std::atomic<std::shared_ptr<Config>> atomic_config;
auto aligned_ptr = std::make_shared<AlignedStruct>();
|
总结:何时使用哪种智能指针?
场景 |
推荐指针 |
说明 |
独占资源 |
unique_ptr |
明确所有权,零开销 |
共享资源 |
shared_ptr |
需要共享访问权 |
缓存/观察 |
weak_ptr |
避免循环引用 |
特殊资源 |
自定义删除器 |
文件句柄、数组等 |
多线程共享 |
atomic |
C++20起可用 |