C++智能指针

一、C++裸指针、内存泄露、new与delete

1.new后应该使用delete手动释放内存

C++裸指针:用new请求动态内存(堆内存),用delete释放内存,不然就会造成内存泄漏。

void func1(){
    int a = 100;
    int *p = new int;
    delete p;
    p = &a;
}

void func2(){
    int *p = new int[100];
    delete[] p;
}

若是不手动写 delete[] p,那么当foo函数执行结束后,p所指向的内存就无人问津了。
因为知道这块地址的所有者p 已经随着函数的结束而消失了。但p所指向的内存还空悬在那里。形成了一个孤岛,别人也用不了。这个现象就叫做内存泄露。

在这里插入图片描述


补充:C++11以后,建议把NULL都换为nullptr,更安全。因为编译器有时会把NULL和int类型搞混。


2.裸指针不可避免地可能犯错的情况:抛异常

1.即使有delete也可能造成内存泄露:抛异常
若p->foo()抛异常,可能会造成delete p 无法被执行,仍然可能造成内存泄露。

void func(){
	Object *p = new Object;
	p->foo();
	delete p;
}

修正:使用智能指针
即便up->foo()抛异常,但资源最后也会被释放

void func(){
	unique_ptr<Object> up { make_unique<Object>()};
	up->foo();
}



二、C++智能指针

避免内存泄露
智能指针可以帮你去管理内存,降低手动管理内存的负担。


0.auto_ptr (已被删除)

在这里插入图片描述


1.unique_ptr (独享指针)

(1)概念

unique_ptr是一种零开销的智能指针。

唯一的指针,不允许其他指针与它共享所指向的对象。
禁止拷贝复制。

一个 std::unique_ptr 同时只能拥有一个对象的所有权,这意味着没有两个 std::unique_ptr 可以同时管理同一个对象。这通过删除拷贝构造函数和拷贝赋值操作符来实现,防止了对象所有权的意外复制。


(2)代码操作

1.头文件、初始化

#include <memory>
using namespace std;

unique_ptr<int> up = make_unique<int>(100)

2.获取裸指针

Ball *p = up->get();

3.智能指针重置
(1)reset方法

up.reset();  //释放unique_ptr下的资源,并把up设置为nullptr
up.reset(new Ball{});  //释放unique_ptr下的资源,并让up指向一个新的Ball实例

(2)release方法 + nullptr
若将智能指针设置为nullptr,则会自动释放所指资源
若使用release()方法,智能指针也会释放资源,并获得一个裸指针指向该资源。此时需要手动管理内存

Ball *ball = up.release();
delete ball;
ball = nullptr;

4.转移控制权
unique_ptr虽然不支持普通拷贝
在这里插入图片描述

但可以通过release转移控制权
在这里插入图片描述

或使用移动语义move
在这里插入图片描述


2.shared_ptr (共享指针)

(1)概念

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源

支持拷贝赋值,允许多个shared_ptr指针指向同一个对象。
用起来更像原始指针,重载了.->操作符。


(2)引用计数

1.概念
共享指针的引用计数会记录有多少个共享指针指向这个对象,当引用计数降为0的时候,程序会自动释放该对象。省去我们手动delete的烦恼。

2.引用计数的具体实现:shared_ptr是如何实现的?
①构造函数中计数初始化为1
②拷贝构造函数中计数值加1
③赋值运算符中,左边的对象计数减1,右边的对象引用加1
④析构函数中引用计数减1
⑤在赋值运算符和析构函数中,如果减1后为0,则调用delete释放对象。

在这里插入图片描述


(3)代码操作

1.头文件

#include <memory> //智能指针头文件

2.初始化

shared_ptr<int> sp;
sp = make_shared<int>(100);  //使用make_shared初始化
shared_ptr<int> sp { make_shared<int>(100) }; //大括号初始化的语法

3.统计引用计数

sp.use_count();

4.智能指针重置

sp.reset(); //当引用计数降为0时,该物体就会被系统自动释放,从而避免内存泄露。

5.想要获得资源的裸指针:get方法 (如:某个函数的参数只接受裸指针)

Ball *p = sp->get();

在这里插入图片描述


(4)问题:循环引用

1.循环引用(环形依赖)
两个智能指针对象相互指向/相互引用,形成环,造成死锁
解决:引入std::weak_ptr,弱引用。其中一个智能指针对象为shared_ptr,一个为weak_ptr

在这里插入图片描述


2.shared_ptr的引用计数导致额外的性能开销


3.weak_ptr

(1)概念

检查资源是否存在。
为打破循环引用而设计。只做观察指针,没有资源的管理权限,不对对象进行操作


(2)使用

1.use_count()
可以观察引用计数的个数

2.提供对原对象的临时访问:lock()
weak_ptr需要操作资源时,需要临时提升为shared_ptr。通过成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。

3.解决循环引用
解决方法就是把一个shared_ptr替换成weak_ptr。



三、智能指针的误用

将同一个裸指针交给不同的智能指针进行托管:用两个智能指针指向同一块堆空间,造成双重释放 double free



四、是否线程安全?

智能指针不是线程安全的,需要设置同步机制。

std::shared_ptr 的引用计数增减与对象释放是线程安全的,但通过std::shared_ptr对封装的指针进行的操作不是线程安全的。


std::unique_ptr本身不是线程安全的,因为它不支持多个线程同时访问同一个std::unique_ptr实例。std::unique_ptr设计用于独占所有权模型,因此在多线程环境中共享std::unique_ptr没有意义。
std::shared_ptr在某些方面是线程安全的。例如,你可以从多个线程安全地创建std::shared_ptr的副本,因为引用计数的增加是原子操作。但是,当你访问std::shared_ptr所管理的资源时,需要额外的同步机制来确保线程安全,因为std::shared_ptr本身不保护所管理对象的线程安全性。【shared_ptr本身是线程安全的,但不保护所管理对象的线程安全性】
总的来说,智能指针提供了一定程度的线程安全性,特别是在管理引用计数方面。但是,当多个线程需要访问由智能指针管理的相同资源时,仍需要考虑额外的线程同步机制

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员爱德华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值