介绍:
- shared_ptr允许多个指针指向同一个对象,对象内存的释放交由智能指针自动释放。
- shared_ptr维护者一个关联计数器,称为引用计数
- 当对一个shared_ptr进行拷贝时,引用计数会加1;当对一个shared_ptr赋予一个新值或shared_ptr被销毁(局部的智能指针变量被销毁)时,引用计数减1
- 当引用计数为0时,会自动调用delete或自定义内存释放函数释放内存
- 释放内存时会调用对象的析构函数
- 引入头文件:#include< memory>
1. 默认构造方式
- shared_ptr< T> sp;
- 默认初始化的智能指针保存的是一个空指针
- 智能指针的使用方式和普通指针类似;解引用一个智能指针放回它指向的对象。
示例:
shared_ptr<int> p1; // 指向int的智能指针,默认初始化为空指针
shared_ptr<string> p2; // 指向string的智能指针,默认初始化为空指针
2. 其他构造方式
默认构造方式只能得到一个空的智能指针,shared_ptr提供了多种其他的构造方式,包括:使用make_shared函数、拷贝构造、与new结合使用。
(1)make_shared函数
- 使用make_shared函数初始化一个智能指针是最安全的分配和使用动态内存的方法
- 使用时必须指定要创建对象的类型
- make_shared(args):返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象
- args要与T类型中的某个构造函数相匹配,make_shared初始化是会调用该类型的对应构造函数;若args为空,则对象进行值初始化
shared_ptr<int> p1 = make_shared<int>(4); //指向一个int的值为4的shared_ptr
shared_ptr<string> p2 = make_shared<string>(4, 's'); // 会调用string(4, 's'),p2指向一个值为"ssss"的string对象
shared_ptr<int> p3 = make_shared<int>(); // args参数为空,使用值初始化。p3指向一个值为0的int型shared_ptr
auto p4 = make_shared<vector<string>>(); // 使用auto自动获取类型,使用值初始化;p4指向一个空的vector<string>
(2)拷贝构造与赋值
| 操作 | 说明 |
|---|---|
| shared_ptr< T> p(q) | p是shared_ptr q的拷贝; 此操作会递增q中的计数器 |
| shared_ptr< T> p(q,d) | p是shared_ptr q的拷贝; 此操作会递增q中的计数器; 对象销毁时,使用对象d代替delete. |
- 拷贝时,引用计数加1
- 赋值时,左值引用计数减1,右值引用计数加1
auto p = make_shared<int>(2); // 创建指向2的智能指针,引用计数为1
auto q(p); // 拷贝,p和q指向相同对象,此时对象有两个引用者,引用计数为2
auto r = make_shared<int>(4); // 创建指向2的智能指针
r = q; // 赋值; 递减r的引用计数,递增q的引用计数;r引用计数变为0,自动释放原来指向的内存
某些情况下,对象本身没有析构函数,智能指针无法使用delete自动释放内存,会导致内存泄漏,因此需要自定义释放内存的方法,初始化智能指针时,将该方法作为参数传递给构造函数。
(3)shared_ptr与new结合使用进行初始化
- 接受指针作为参数的智能指针的构造函数是explicit的,即不能将一个内置指针隐式转换为一个智能指针
- 默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针是使用delete释放对象内存的,而不是free。
| 操作 | 说明 |
|---|---|
| shared_ptr< T> p(q) | q是内置指针,必须指向new分配的内存; p管理内置指针q所指向的对象 |
| shared_ptr< T> p(u) | p从unique_ptr u那里接管了对象的控制权; u会置为空 |
| shared_ptr< T> p(q,d) | q是内置指针,必须指向new分配的内存; p管理内置指针q所指向的对象 对象销毁时,使用对象d代替delete. |
示例
shared_ptr<int> p1; // 默认初始化,p1为空的智能指针
shared_ptr<int> p1(new int(2)); // 使用指向动态内存的普通指针初始化
下面的这种方式是错误的,因为无法进行隐式转换,不能将普通指针直接赋值给智能指针
shared_ptr<int> p3 = new int(4); // 错误,无法直接赋值
shared_ptr<int> f(int p) {
return new int(p); // 错误,无法隐式转换为shared_ptr<int>
}
3. shared_ptr其他操作
| 操作 | 说明 |
|---|---|
| ** p.get() ** | 返回智能指针p中保存的指针(普通指针) |
| swap(p, q) p.swap(q) | 交换p和q中的指针 |
| p.use_count() | 返回与p共享的智能指针数量; 主要用于调试. |
| p.unique() | 若p.use_count()为1,返回true;否则返回false |
| p.reset() p.reset(q) p.reset(q, d) | 若p是唯一指向其对象的智能指针,reset会释放此对象。 若传递了对象q,则p指向q,否则p置为空; 使用d替换delete释放内存 |
4. shared_ptr使用的注意事项
(1)尽量不要混合使用普通指针和智能指针
混合使用普通指针和智能指针,可能导致智能指针已经释放,而普通指针依然指向那块已释放的内存,导致出现空悬指针。
示例1-正确方式(但也不建议)
void process(shared_ptr<int> ptr) {
cout << *ptr << endl;
} // 函数运行结束时,局部变量ptr被销毁,但其指向的内存不一定释放的
shared_ptr<int> p(new int(4)); // 引用计数为1
process(p); // 拷贝p会递增p的引用计数;在process函数中引用计数为2;结束调用后引用计数又变为1
int i = *p; // 正确,此时p的引用计数为1
示例2-错误方式
void process(shared_ptr<int> ptr) {
cout << *ptr << endl;
} // 函数运行结束时,局部变量ptr被销毁,但其指向的内存不一定释放的
int *p(new int(4)); // p是普通的指针,指向动态内存,不是智能指针
process(shared_ptr<int>(p)); // 通过普通指针初始化一个智能指针,p所指向的内存有智能指针托管;
// process执行完之后,引用计数变为0,内存会被释放
int i = *p; // 错误,由于内存已被智能指针释放,p变成一个空悬指针,出现访问异常
(2)尽量不要使用get初始化另一个指针或为智能指针赋值
get()函数会返回智能指针中的一个内置指针,指向智能指针管理的对象。该函数的使用用途:向一个不能使用智能指针的代码传递一个内置指针。使用get返回的指针的代码不能delete此指针。
示例:以下用法会出错,但编译器编译时不会发现
shared_ptr<int> p(new int(4)); // 引用计数为1
int *q = p.get(); // 普通指针,指向智能指针指向的内存,因此使用q时,要确保内存未被释放掉
{ // 新程序块
shared_ptr<int>(q); // 使用普通指针初始化临时智能指针,引用计数为1
} // 程序块执行后,q被销毁,局部变量智能指针引用计数变为0,指向的内存被释放了。
int a = *p; // 错误,p所指向的内存已经被释放了
p和q指向相同的内存,他们是相互独立创建的,因此各自的引用计数都为1;当程序块运行结束后,q被销毁,q指向的内存被释放。p也变成了一个空悬指针,再使用p会引发异常。
本文详细介绍了C++中的智能指针shared_ptr,包括其默认构造、其他构造方式,如make_shared、拷贝构造和与new的结合使用。同时,强调了shared_ptr的引用计数机制及其在内存管理中的作用,以及使用shared_ptr时需要注意的事项,如避免混合使用普通指针和智能指针,以及避免使用get初始化其他指针。
9389

被折叠的 条评论
为什么被折叠?



