智能指针详解

news2024/11/17 11:35:52

概念

 在c++中,动态内存的管理式通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。动态内存的使用很容易出现问题,因为确保在正确的时间释放内存是极其困难的。有时使用完对象后,忘记释放内存,造成内存泄漏的问题。

  所谓的智能指针本质就是一个类模板,它可以创建任意的类型的指针对象,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间。

在这里我们可以定义一个字面意思的自实现的智能指针

#include <iostream>
using namespace std;
template <class T>
class test
{
private:
	T* ptr;
public:
	test(T* ptr = nullptr):ptr(ptr){}
	~test
	{
		if (ptr != nullptr)
		{
			delete ptr;
			ptr = nullptr;
		}
	}
};

这样的设计也就是体现了我们前面提到的智能指针的特点——实际上是一个类模板可以适应泛型编程

智能指针和普通指针的使用方法类似,因此我们需要在设计自实现的智能指针中加上 * ->的重载

#include <iostream>
using namespace std;
template <class T>
class test
{
private:
	T* ptr;
public:
	test(T* ptr = nullptr):ptr(ptr){}
	~test
	{
		if (ptr != nullptr)
		{
			delete ptr;
			ptr = nullptr;
		}
	}
	T& operator*()
	{
		return *ptr;
	}
	T* operator->()
	{
		return ptr;
	}
};

我们可以通过*和->去获得指向的对象

智能指针的特点就是当程序结束时,此时ptr1和ptr2指针被销毁时,对象ptr1和ptr2会自动调用析构函数去释放所指向的资源 

int main()
{
	test<int>ptr(new int);
	test<int>ptr1(ptr);
	return 0;
}

由于我们没有重写他的拷贝构造函数,因此在拷贝构造时就会出现问题,因为ptr和ptr1对象指向的是同一块内存空间,因此在程序结束调动析构函数析构时,就会对同一个资源对象进行析构,就会引发问题,因此我们就需要让拷贝构造和赋值只能释放一次内存资源。

由此我们引向C++提供的智能指针

C++智能指针库

1.auto_ptr

auto_ptr是c++98版本库中提供的智能指针,该指针解决上诉的问题采取的措施是管理权转移的思想,也就是原对象拷贝给新对象的时候,原对象就会被设置为nullptr,此时就只有新对象指向一块资源空间。

 

int main()
{
	auto_ptr<int>ptr(new int);
	auto_ptr<int>ptr1(ptr);//将ptr的资源给ptr1之后就会将ptr置为nullptr
}

 那么这个时候问题就出现了,因为将ptr置为空,如果后续在使用了ptr,就对空指针进行了操作,这会使整个程序崩溃,因此auto_ptr逐渐被废弃

2.unique_ptr

unique_ptr是c++11版本库中提供的智能指针,它直接将拷贝构造函数和赋值重载函数给禁用掉,因此,不让其进行拷贝和赋值。

unique_ptr是C++11提供的用于防止内存泄漏的智能指针中的一种实现,独享被管理对象指针所有权的智能指针。unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。

unique_ptr对象始终是关联的原始指针的唯一所有者,实现了独享所有权的语义。一个非空的unique_ptr总是拥有它所指向的资源。转移一个unique_ptr将会把所有权也从源指针转移给目标指针(源指针被置空)。拷贝一个unique_ptr将不被允许,因为如果你拷贝一个unique_ptr,那么拷贝结束后,这两个unique_ptr都会指向相同的资源,它们都认为自己拥有这块资源(所以都会企图释放)。因此unique_ptr是一个仅能移动的类型。当指针析构时,它所拥有的资源也被销毁。默认情况下,资源的析构是伴随着调用unique_ptr内部的原始指针的delete操作的。

unique_ptr具有->和*运算符重载符,因此它可以像普通指针一样使用。

3.shared_ptr

share_ptr是c++11版本库中的智能指针,shared_ptr允许多个智能指针可以指向同一块资源,并且能够保证共享的资源只会被释放一次,因此是程序不会崩溃 

int main()
{
	shared_ptr<int>ptr(new int);
	shared_ptr<int>ptr1(ptr);
	shared_ptr<int>ptr2;
	ptr2 = ptr1;
	cout << ptr.get() << endl;
}

那么shared_ptr实现多个智能指针指向一块资源并且能保证共享的资源只被释放一次呢;

这里我们就引入了一个引用计数,我们用画图来表述意思

 

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

shared_ptr在内部会维护着一份引用计数,用来记录该份资源被几个对象共享。
当一个shared_ptr对象被销毁时(调用析构函数),析构函数内就会将该计数减1。
如果引用计数减为0后,则表示自己是最后一个使用该资源的shared_ptr对象,必须释放资源。
如果引用计数不是0,就说明自己还有其他对象在使用,则不能释放该资源,否则其他对象就成为野指针。
引用计数是用来记录资源对象中有多少个指针指向该资源对象。
实际上引用计数的内部实际上是存放在堆上的一个count,所有的线程都能对其访问,在多个线程修改该值时,就会出现线程安全问题,因此我们需要对在修改他的时候加锁保护

 ****

shared_ptr的缺点

1.shared_ptr固然好用,但是它也会有问题存在。假设我们要使用定义一个双向链表,如果我们想要让创建出来的链表的节点都定义成shared_ptr智能指针,那么也需要将节点内的_pre和_next都定义成shared_ptr的智能指针。如果定义成普通指针,那么就不能赋值给shared_ptr的智能指针。

2.当其中两个节点互相引用的时候,就会出现循环引用的现象。

当创建出node1和node2智能指针对象时,引用计数都是1.
当node1的next指向node2所指向的资源时,node2的引用计数就+1,变成2,node2的pre指向noede1所指向的资源时,node1的引用计数+1,变成2.
当这两个智能指针使用完后,调用析构函数,引用计数都-1,都变成1,由于引用计数不为0,所以node1和node2所指向的对象不会被释放。
当node1所指向的资源释放需要当node2中的_prev被销毁,就需要node2资源的释放,node2所指向的资源释放就需要当node1中的_next被销毁,就需要node1资源的释放。因此node1和node2都有对方的“把柄”,这两个就造成循环引用现象,最终这node1和node2资源就不会进行释放。

4.weak_ptr

weak_ptr这个指针天生一副“小弟”的模样,也是在C++11的时候引入的标准库,它的出现完全是为了弥补它老大shared_ptr天生有缺陷的问题,其实相比于上一代的智能指针auto_ptr来说,新进老大shared_ptr可以说近乎完美,但是通过引用计数实现的它,虽然解决了指针独占的问题,但也引来了引用成环的问题,这种问题靠它自己是没办法解决的,所以在C++11的时候将shared_ptr和weak_ptr一起引入了标准库,用来解决循环引用的问题。

weak_ptr本身也是一个模板类,但是不能直接用它来定义一个智能指针的对象,只能配合shared_ptr来使用,可以将shared_ptr的对象赋值给weak_ptr,并且这样并不会改变引用计数的值。
 

weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快。表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。

weak特点

weak_ptr虽然是一个模板类,但是不能用来直接定义指向原始指针的对象。
weak_ptr接受shared_ptr类型的变量赋值,但是反过来是行不通的,需要使用lock函数。
weak_ptr设计之初就是为了服务于shared_ptr的,所以不增加引用计数就是它的核心功能。
由于不知道什么之后weak_ptr所指向的对象就会被析构掉,所以使用之前请先使用expired函数检测一下。
 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/548646.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

gitlab建立新分支提交,cherry-pick部分更新

gitlab介绍 GitLab是一个基于Git的在线代码托管和协作平台&#xff0c;提供源代码管理、单元测试、CI/CD构建、代码审查等功能。它是一个开放源代码的Git仓库管理系统&#xff0c;使用 Ruby on Rails 构建GitLab 不仅具有自己的 Git 仓库管理系统&#xff0c;还具有很多其他的…

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具

本篇文章聊聊如何使用 GPT 快速完成一个开源小项目&#xff0c;解决实际的问题&#xff0c;顺手点亮 GitHub 上 Nginx 开源社区的贡献者图标。 “Talk is Cheap&#xff0c;Show you the Code。” 写在前面 整理了一篇本该上个月就发出的内容。 前段时间&#xff0c;有个投…

浅谈JDK8的垃圾回收器

JDK1.8默认使用Parallel Scavenge作为年轻代的垃圾回收器,使用Parallel Old作为老年代的垃圾回收器&#xff0c;又称为PS MarkSweep。 Parallel Scavenge 收集器 Parallel Scavenge收集器又称为吞吐量优先收集器&#xff0c;和ParNew收集器类似&#xff0c;是一个新生代收集器。…

【OpenCV DNN】Flask 视频监控目标检测教程 01

欢迎关注『OpenCV DNN Youcans』系列&#xff0c;持续更新中 【OpenCV DNN】Flask 视频监控目标检测教程 01 【OpenCV DNN】Flask 视频监控目标检测教程 01 1. 面向Python程序的Web框架2. Flask 框架的安装与使用2.1 Flask 安装2.2 Flask 框架例程2.3 绑定IP和端口2.4 Flask路…

2023-5-20基于52单片机的智能家居系统(蓝牙)

资料已上传在微信公众号&#xff1a;风吹摇铃 奔赴星海 此系统可根据开发板原理图搭配外载模块实现功能&#xff0c;也可以根据原理图焊接或者PCB焊接。 注意&#xff1a;根据开发板搭载外部模块实现功能&#xff0c;需根据开发板原理图修改代码 0、整理及编写了19个常用的5…

NameServer路由注册与发现

NameServer在RocketMQ中主要承担的就是路由的管理、服务注册、以及服务的发现。在RocketMQ这承担着很重要的责任。 整体架构&#xff1a; 消息生产者在发送消息前需要考虑的问题就是&#xff0c;我需要发给谁&#xff1f;地址在哪儿&#xff1f;对于消费者也一样。那么NameSer…

软件工程 | 期末复习

一、软件与软件危机 1、软件发展经历三个阶段&#xff1a;程序设计、程序系统、软件工程 2、软件的概念&#xff1a;软件是计算机系统与硬件相互依存的另一部分&#xff0c;包括程序、数据以及相关文档的完整集合&#xff0c;软件程序数据文档 数据&#xff1a;使程序能够适…

测试人员转型是大势所趋:我的十年经验告诉我,你必须要行动起来了。

做测试十多年&#xff0c;有不少人问过我下面问题&#xff1a; 现在的手工测试真的不行了吗&#xff1f; 测试工程师&#xff0c;三年多快四年的经验&#xff0c;入门自动化测试需要多久&#xff1f; 自学自动化测试到底需要学哪些东西&#xff1f; 不得不说&#xff0c;随着行…

学习open62541 --- [76] 使用智能指针处理内存释放问题

在使用监测项时&#xff0c;一般都会加一个context&#xff0c;然后在回调函数里使用这个context&#xff0c;这就需要保证context的内存空间在执行回调函数时是有效的。往往有以下三种方法&#xff1a; 使用静态内存空间&#xff1a;使用static创建静态变量&#xff0c;然后把…

【Python 爬虫常见的报错及其解决方法】零基础也能轻松掌握的学习路线与参考资料

Python 爬虫被广泛应用于数据采集和分析。然而&#xff0c;爬虫在运行过程中常常会遇到各种问题和错误&#xff0c;降低了爬虫效率、准确性和可靠性。因此掌握爬虫常见报错及其解决方法是非常关键的。本文将介绍 Python 爬虫常见的报错及其解决方法&#xff0c;并提供参考资料和…

内存泄漏的原因,内存泄漏如何避免?内存泄漏如何定位?

1. 内存溢出 内存溢出 OOM &#xff08;out of memory&#xff09;&#xff0c;是指程序在申请内存时&#xff0c;没有足够的内存空间供其使用&#xff0c;出现out of memory&#xff1b;比如申请了一个int,但给它存了long才能存下的数&#xff0c;那就是内存溢出。 2. 内存泄…

PyTorch LSTM和LSTMP的原理及其手写复现

PyTorch LSTM和LSTMP的原理及其手写复现 0、前言全部参数的细致介绍代码实现Reference 0、前言 关于LSTM的原理以及公式其实在这篇博客一步一步详解LSTM网络【从RNN到LSTM到GRU等&#xff0c;直至attention】讲的非常清晰明了了。 这里就是写出LSTM的pytorch的实现&#xff0c;…

【随笔记】全志 T507 PF4 引脚无法被正常设置为中断模式的问题分析

相关信息 硬件平台&#xff1a;全志T507 系统版本&#xff1a;Android 10 / Linux 4.9.170 问题描述&#xff1a;PF4 无法通过标准接口设置为中断模式&#xff0c;PF1、PF2、PF3、PF5 都可以。 分析过程 一开始以为是引脚被其它驱动占用引起&#xff0c;或者该引脚不具备中断…

高光谱成像技术在果蔬品质检测中的应用

在当前市场经济背景下&#xff0c;食品安全问题是消费者最为关心的问题之一&#xff0c;尤其是果蔬产品&#xff0c;农药残留问题和品质问题直接关系着消费者的权益和人身安全。针对传统化学检测的缺陷&#xff0c;本文结合高光谱成像技术&#xff0c;对其在果蔬品质与安全无损…

【C++】多态的概念/重写/虚表/抽象类

多态 多态的概念多态的定义和实现重写抽象类多态的原理虚表的构建原理虚函数的调用原理 多态的概念 多态就是多种形态&#xff0c;传递不同的对象&#xff0c;会调用不同的方法。 多态的定义和实现 那么在C语法中&#xff0c;多态是如何实现的呢&#xff1f; 我们首先要在继承…

vue学习 - 基础篇

初始工程结构 这里我们使用script标签从cdn获取vue.js, 而不是使用脚手架vue-cli, 因为cdn比较方便一点, 也不用配置node之类的比较麻烦 index.html <!DOCTYPE html> <html><head><title>VueJS Course</title><link rel"stylesheet"…

第三篇、基于Arduino uno,用oled0.96寸屏幕显示dht11温湿度传感器的温度和湿度信息——结果导向

0、结果 说明&#xff1a;先来看看拍摄的显示结果&#xff0c;如果是你想要的&#xff0c;可以接着往下看。 1、外观 说明&#xff1a;本次使用的oled是0.96寸的&#xff0c;别的规格的屏幕不一定适用本教程&#xff0c;一般而言有显示白色、蓝色和蓝黄一起显示的&#xff0…

RabbitMQ日常使用小结

一、使用场景 削峰、解耦、异步。 基于AMQP(高级消息队列协议)协议来统一数据交互,通过channel(网络信道)传递信息。erlang语言开发&#xff0c;并发量12000&#xff0c;支持持久化&#xff0c;稳定性好&#xff0c;集群不支持动态扩展。 RabbitMQ的基本概念 二、组成及工作流…

可见性原子性有序性的+线程传参的方式+Java如何实现多个线程之间共享数据+线程间通信+死锁产生

//为了均衡CPU和内存的速度差异,增加了缓存 导致了可见性的问题; //操作系统增加了进程 线程 分时复用CPU,均衡CPU和io设备的速速差异 导致了原子性问题; //jvm指令重排序(优化指令排序) 导致了有序性的问题 可见性问题是指 线程A修改共享变量,修改后CPU缓存中的数据没有及时同…

Emacs之目前最快补全插件lsp-bridge(八十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…