C++智能指针的使用-shared_ptr详解

本文详细介绍了C++中的智能指针shared_ptr,包括其默认构造、其他构造方式,如make_shared、拷贝构造和与new的结合使用。同时,强调了shared_ptr的引用计数机制及其在内存管理中的作用,以及使用shared_ptr时需要注意的事项,如避免混合使用普通指针和智能指针,以及避免使用get初始化其他指针。

介绍:

  • 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会引发异常。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值