智能指针介绍及使用
本文介绍了C++智能指针的实现原理与使用场景。通过RAII机制,智能指针将资源生命周期与对象绑定,确保资源自动释放。对比分析了三种主要智能指针:unique_ptr(独占所有权)、shared_ptr(共享所有权,引用计数)和weak_ptr(弱引用)。详细阐述了各自的实现原理、性能特点及适用场景,如unique_ptr适用于独占资源,shared_ptr用于资源共享,weak_ptr则用于解决循
目录
前言
智能指针是现代C++管理资源的核心工具,它极大降低内存泄漏和资源管理错误的风险。
本文将简要介绍其底层实现机制,以及各种智能指针的设计差异、适用场景。
一、智能指针的使用原理
在C++中,如果用 new 去分配内存,但忘记用 delete 释放,便会导致内存泄漏。
同时,如果程序异常退出,delete 语句便无法执行。
void Func()
{
int *a1=new int;
int *a2=new int;
cout<<div()<<endl;//若此处抛出异常,a1、a2都会无法释放。
delete a1;
delete a2;
}
所以,C++引用了 智能指针 来解决这一问题,底层思想为· RAll。
二、RAll原理
RAll 是 C++ 中一种将资源的生命周期与对象的生命周期绑定的核心技术,指通过对象的构造和析构来管理资源。
- 构造时,获取资源。(如内存、文件句柄、锁、数据库连接等)
- 析构时,释放资源。
其本质为:只要把资源封装到对象里,就可以自动管理资源的生命周期。
【优势】:
C++保证,无论函数是正常退出还是因为异常导致栈展开,所有局部对象的析构函数都会被调用。这确保了资源总是能被正确释放,极大地避免了资源泄漏。
| 特性 | unique_ptr | shared_ptr | weak_ptr |
|---|---|---|---|
| 所有权 |
独占所有权 一个对象只能同时被一个unique_ptr拥有。 |
共享所有权 一个对象可以被多个shared_ptr拥有,采用引用计数。对象在所有拥有它的shared_ptr被销毁后释放。 |
弱引用 不拥有对象,不增加引用计数。它指向一个由shared_ptr管理的对象。 |
| 实现原理 | 拷贝构造和拷贝赋值被禁用 | 控制块存储引用计数(use count)和弱计数(weak count)。拷贝时引用计数+1,析构时-1,减至0时释放对象资源。 | 内部持有指向shared_ptr控制块的指针。可以通过lock()方法尝试获取一个指向对象的shared_ptr(如果对象还存在的话)。它不改变引用计数,但会影响控制块的弱计数。 |
| 性能 | 零开销,和裸指针几乎一样 | 有额外开销:需要动态分配控制块,引用计数的增减需要原子操作(线程安全),有一定性能损耗。 | 同样有控制块开销,lock()也需要原子操作。 |
【注】:
1.shared_ptr 弥补了 unique_ptr 的不足,通过引用计数实现多智能指针共享同一资源。
-
每块被管理的资源都会关联一个引用计数器。
-
拷贝时引用计数+1,析构时-1,减至0时释放对象资源。
2.shared_ptr 虽然实现了资源共享,但引用计数本身无法检测对象之间是否形成循环引用,从而导致资源无法释放。
3.为解决循环引用造成的内存泄露,C++引用了 weak_ptr(解决循环引用)。
四、各智能指针适用场景
(一)unique_ptr
- 需要多态行为的独占资源:例如,在策略模式中持有具体的策略对象。
- 作为类的成员变量(组合关系):表示容器拥有这个成员,生命周期与容器一致。
(二)shared_ptr
- 多个所有者:例如,多个线程或对象需要共享同一个资源,只有当最后一个使用者销毁时才释放。
- 复杂的数据结构:但要注意,在双向链表或图中要避免循环引用。
(三)weak_ptr
- 打破循环引用:这是它最主要的设计目的。
- 缓存:缓存对象可以用shared_ptr管理,而外部引用可以用weak_ptr。当缓存对象不再被任何地方使用时(引用计数为0),它会被释放,weak_ptr自动悬空。再次使用时,通过lock()尝试提升,如果成功则直接使用,否则重新加载。
- 观察者模式:观察者持有被观察者的weak_ptr,可以随时检查被观察者是否还存活,而不会影响其生命周期。
五、避免使用智能指针的场景
- 为了避免引用循环而引入过于复杂的weak_ptr设计时
如果一个类的设计一开始就充满了shared_ptr和weak_ptr的互相指涉,代码可读性和维护性会大大降低。有时一个清晰的所有权设计(如明确的父子关系,父亲拥有孩子,孩子持有父亲的裸指针)比复杂的智能指针组合更可取。
- 自定义内存池
如果使用了特定的内存池(如对象池),资源的分配和释放由池自己管理,再用智能指针去自动释放就会造成冲突(双重释放)。此时可能只需要用裸指针,或者配合自定义删除器来将对象归还给池,而不是直接delete。
更多推荐

所有评论(0)