C++11——智能指针

智能指针的介绍

智能指针是C++11中引入的标准库特性之一,智能指针是为了避免手动管理内存时常见的错误,比如内存泄漏、重复释放内存等问题。智能指针通过封装原生指针(裸指针)和自动释放内存的功能,让开发者更安全和高效地管理动态资源。

C++11为了解决手动管理内存时容易出现的内存泄漏问题,提供了三种主要的智能指针类型:

  1. std::unique_ptr:用于独占所有权的智能指针。一个对象只能有一个unique_ptr指向它,且不能被通过拷贝构造来进行复制,但可以通过std::move转移所有权。
  2. std::shared_ptr:用于共享所有权的智能指针。多个shared_ptr可以指向同一个对象,当最后一个指针销毁时,对象才会被删除。
  3. std::weak_ptr:与shared_ptr配合使用,用于避免循环引用。它不影响引用计数,不能直接访问资源,但可以通过lock()获得一个有效的shared_ptr。

内存泄露

内存泄漏指的是程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

对于长期运行的程序出现内存泄漏,其影响很大,如操作系统、后台服务等,出现内存泄漏会导致响应越来越慢,最终逐渐卡死。

智能指针

RAII

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是C++中一种非常重要的资源管理惯用法。其核心思想是将资源的获取与对象的生命周期绑定,通过对象的创建和销毁来自动管理资源的分配与释放(构造获取资源,析构释放资源)。RAII的主要目的是确保无论程序控制流如何变化,当内存资源在不再使用时,都能够被正确地释放。

下面是一个基于RAII思想设计出的一个简单的智能指针:

template<class T>
class SmartPtr
{
private:
	T* _ptr = nullptr;
public:
	SmartPtr(T* ptr)//构造时获取资源
		:_ptr(ptr)
	{}
	~SmartPtr()//析构时释放资源
	{
		if (_ptr != nullptr)//判断_ptr是否提前被释放,避免二次析构释放资源
		{
			delete _ptr;
		}
	}
    //重载*、->使得智能指针可以像原生指针一样正常使用
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
};

基于RAII思想,智能指针本质就是借助了对象来托管指针存放的资源。

对于对象来说,当其生命周期结束后会自动调用析构函数来释放资源, 这对于处理内存泄露的问题十分方便,而通过使用智能指针,我们也不需要手动显示的去释放开辟的资源。

std::unique_ptr 智能指针

在C++11中首先引入了unique_ptr这一智能指针,一个对象不能由多个unique_ptr来管理,只能被一个unique_ptr来管理。此外,为了避免unique_ptr之间发生拷贝构造导致多个unique_ptr指向同一个对象,unique_ptr中的拷贝构造函数、以及赋值重载函数是不允许被使用的

unique_ptr的简单模拟实现

template<class T>
class UniquePtr
{
private:
	T* _ptr = nullptr;
public:
	UniquePtr(T* ptr)//构造时获取资源
		:_ptr(ptr)
	{}
	~UniquePtr()//析构时释放资源
	{
		if (_ptr != nullptr)//判断_ptr是否提前被释放,避免二次析构释放资源
		{
			cout << "delete _ptr" << endl;
			delete _ptr;
		}
	}
	//禁用拷贝构造函数以及赋值重载函数
	UniquePtr(const UniquePtr<T>& uptr) = delete;
	UniquePtr<T>& operator= (const UniquePtr<T>& uptr) = delete;

	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
};

以上对于unique_ptr智能指针进行了简单的模拟实现,实际上还有许多功能没有实现。关于unique_ptar主要需要注意的点,在于需要禁掉拷贝构造函数以及赋值重载函数的使用,避免多个unique_ptr同时指向同一个对象。

std::shared_ptr 智能指针

与unique_ptr不同的是,shared_ptr是可以允许多个shared_ptr智能指针同时指向同一个对象。

也就是shared_ptr中并没有禁用拷贝构造函数以及赋值重载函数。但为了避免二次析构释放资源的问题,shared_ptr还采取了其他策略。

shared_ptr的原理

shared_ptr智能指针可以指向同一个对象,这难免会有同一个对象资源重复被释放的问题,为了解决这个问题,shared_ptr中引进了新的成员变量。

C++11通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

在shared_ptr内部,通过成员变量,给每个资源都维护了着一份计数,用来记录该份资源被几个对象享。
在对象被销毁时(析构函数调用),就说明自己不使用该资源了,对象的引用计数减1,当引用计数不为0时,说明仍然有对象对资源进行管理,只有当引用计数为0时才对管理资源进行释放。

 shared_ptr的简单模拟实现

template<class T>
class SharedPtr
{
private:
	T* _ptr;
	int* _count;//记录管理资源空间的对象个数
public:
	SharedPtr(T* ptr = nullptr)
		:_ptr(ptr)
		,_count(new int(1))
	{}
	//拷贝构造
	SharedPtr(const SharedPtr<T>& sp)
	{
		if (*_count == 1)//资源管理对象只剩自身一个
		{
			~SharedPtr();
		}
		_ptr = sp._ptr;
		_count = sp._count;
		*_count += 1;
	}
	//赋值重载
	SharedPtr<T>& operator = (const SharedPtr<T>& sp)
	{
		if (this != &sp)//避免自身赋值
		{
			if (*_count == 1)//资源管理对象只剩自身一个
			{
				this->~SharedPtr();
			}
			_ptr = sp._ptr;
			_count = sp._count;
			*_count += 1;//管理资源对象加一
		}
		return *this;
	}
	~SharedPtr()
	{
		if (_ptr!=nullptr && *_count == 1)//_ptr不为空,且资源管理对象只剩自身一个
		{
			cout << "delete _ptr:" << _ptr << endl;
			cout << "delete _count:" << _count << endl;
			delete _ptr;
			delete _count;
		}
        else if (_ptr != nullptr && *_count > 1)//_ptr不为空,且资源管理对象不止一个
        {
	        *_count -= 1;//管理资源对象减一
        }
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
    T* get()const
    {
	    return _ptr;
    }
	friend ostream& operator<< (ostream& out, const SharedPtr<T>& sp)
	{
		out << sp._ptr;
		return out;
	}
};

shared_ptr的模拟实现比较需要注意的是,每个对象的资源释放问题,每次赋值重载需要考虑是否原先管理的资源是否需要释放资源。

 std::shared_ptr的循环引用

在一些特定的场景下,shared_ptr的使用有时会出现问题,对于使用shared_ptr循环引用的场景,其不能正常实现对资源的管理。

struct ListNode
{
	int _data; 
	SharedPtr<ListNode> _prev;
	SharedPtr<ListNode> _next;
	~ListNode() 
	{ 
		cout << "~ListNode()" << endl;
	}
};

例如对于 某一上双向链表中,将前后指针使用shared_ptr来管理资源,此时就会出现循环引用的问题。

如果将两链表节点相互连接起来,使得node1的节点的后继节点指向node2节点,node2的前驱节点指向node1,此时两节点中的指针成员相互被连接起来,node1以及node2节点的引用计数都为2

 随着生命周期的结束,node1、node2的资源要被析构释放, 引用计数各自减为1,但是node1成员中的_next还是指向node2,node2中的_pre也还是指向node1,这会导致,node1中shared_ptr管理的_next的释放需要将node2释放,而node2中shared_ptr管理的_prev的释放需要将node1释放,导致两者相互制约,哪个都不会先释放

而为了类似这种循环引用的问题,后续C++11又引入了新的智能指针,也就是第三个智能指针所提std::weak_ptr。 

std::weak_ptr智能指针

 weak_ptr智能指针用于解决shared_ptr中的循环引用问题,weak_ptr只能用于托管shared_ptr智能指针,其本质是在不增加引用计数的前提下接收shared_ptr中管理的资源。

使用weak_ptr托管shared_ptr时,管理相同资源时,其管理对象的个数的引用计数也不会增加计数,这避免了循环引用导致的问题。

weak_ptr的简单模拟实现

template<class T>
class WeakPtr
{
private:
	T* _ptr;
public:
	WeakPtr(const SharedPtr<T> & sp = nullptr)
		:_ptr(sp.get())
	{}
	WeakPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		_ptr = sp.get();
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	//SharedPtr会管理释放对象的资源
};

对于weak_ptr的模拟实现实际上很简单,只需要接收shared_ptr的对象,将shared_ptr中管理的资源交给weak_ptr一同管理,不同的是weak_ptr实际只是起到一个传递指针的作用,参与管理资源并没有被shared_ptr中的引用计数记录。 

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[渗透]前端源码Chrome浏览器修改并运行

文章目录 简述本项目所使用的代码[Fir](https://so.csdn.net/so/search?qFir&spm1001.2101.3001.7020) Cloud 完整项目 原始页面修改源码本地运行前端源码修改页面布局修改请求接口 本项目请求方式 简述 好久之前&#xff0c;就已经看到&#xff0c;_无论什么样的加密&am…

SPI的学习

工作原理 SPI的工作原理基于主从架构。主设备通过四条主要信号线与一个或多个从设备进行通信&#xff1a; MOSI&#xff08;主输出&#xff0c;从输入&#xff09;DI&#xff08;Master Output Slave Input&#xff09;&#xff1a;主设备发送数据到从设备。MISO&#xff08;…

利用自定义 ref 实现函数防抖

今天来简单介绍一个新的方法&#xff0c;使用自定义 ref 实现函数防抖。 1. 自定义 ref 的来源 自定义 ref 防抖函数来自于前端开发中的两个概念&#xff1a;Vue 的响应式系统 和 数防抖&#xff08;Debounce&#xff09;。 1、Vue 响应式系统&#xff1a;Vue 提供了 ref 和…

SQL 干货 | SQL 反连接

最强大的 SQL 功能之一是 JOIN 操作&#xff0c;它提供了一种优雅而简单的方法&#xff0c;将一个表中的每一条记录与另一个表中的每一条记录结合起来。不过&#xff0c;有时我们可能想从一个表中找到另一个表中没有的值。正如我们将在今天的博客文章中看到的&#xff0c;通过包…

爬虫结合项目实战

由于本人是大数据专业&#xff0c;所以准备的是使用pycharm工具进行爬虫爬取数据&#xff0c;然后实现一个可视化大屏 参考项目&#xff1a; 1.医院大数据可视化最后展示 2. 大数据分析可视化系统展示 代码包&#xff1a;

会话管理——Cookie

会话管理在人机交互中扮演着至关重要的角色&#xff0c;它是指保持用户的整个会话活动的互动与计算机系统跟踪过程。以下是对会话管理的简单介绍&#xff1a; 会话主要分为两类&#xff1a;有状态会话&#xff08;知道对方身份&#xff09;和无状态会话&#xff08;不知道对方…

go mod的使用

1. go中包的介绍和定义 包就是很多golang源码的集合&#xff0c;Go语言为我们提供了很多内置包&#xff0c;如fmt、strconv、strings、sort、errors、time、encoding/json、os、io等。 种类&#xff1a;1、系统内置包 2、自定义包 3、第三方包 系统内置包&…

计算机组成原理一句话

文章目录 计算机系统概述存储系统指令系统 计算机系统概述 指令和数据以同等地位存储在存储器中&#xff0c;形式上没有差别&#xff0c;但计算机应能区分他们。通过指令周期的不同阶段。 完整的计算机系统包括&#xff0c;1&#xff09;软件系统&#xff1a;程序、文档和数据&…

GEE引擎架设好之后进游戏时白屏的解决方法——gee引擎白屏修复

这两天测试GeeM2引擎的服务端&#xff0c;最常见的问题就是点击开始游戏出现白屏&#xff0c;最早还以为是服务端问题&#xff0c;结果是因为升级了引擎&#xff0c;而没有升级NewUI这份文件导致的。解决方法如下&#xff1a; 下载GEE引擎包最新版&#xff0c;&#xff08;可以…

C++实现精简实用的json解析库

fcjson.h #pragma once#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) #define _CRT_SECURE_NO_WARNINGS #endif#include <stdint.h> #include <string> #include <vector> #include <map>// VS 将执行字符集指定为…

Jupyter Notebook修改你的默认路径

Jupyter Notebook修改你的默认路径 看到网上一堆抄来抄去的博客&#xff0c;图片和文字都不对应&#xff0c;搞得配置得头昏脑涨的&#xff0c;所以写了这个。 1.打开 Anaconda Prompt 2.输入 jupyter notebook --generate-config&#xff0c;注意提示后输入N&#xff0c;因为…

【K8s】Kubernetes 证书管理工具 Cert-Manager

本文内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01; 如果对您有帮助&#xff0c;烦请点赞、关注、转发、订阅专栏&#xff01; 专栏订阅入口 | 精选文章 | Kubernetes | Docker | Linux | 羊毛资源 | 工具推荐 | 往期精彩文章 【Docker】&#xff08;全…

校招基础知识详解——计算机操作系统(内存管理)

文章目录 虚拟内存分页系统地址映射页面置换算法最佳页面置换算法(OPT, Optimal replacement algorithm)先进先出置换算法&#xff08;FIFO, First In First Out&#xff09;最近最久未使用的置换算法&#xff08;LRU, Least Recently Used&#xff09;最不常用算法最近未使用&…

Excel常用操作培训

1 Excel基本操作 1.1 常用快捷键 1.1.1快捷键操作工作簿、工作表 1.1.2快捷键操作 1.1.3单元格操作 1.1.4输入操作 2.1 常见功能描述 2.1.1 窗口功能栏 excel有很多功能可以用&#xff0c;新建文档后&#xff0c;可以最上方&#xff0c;可以看到所有的功能栏目 2.1.2 剪切板…

《YOLO目标检测》—— YOLO v2 详细介绍

&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;未写完&#xff01;&#xff01;&#xff01;&#xff0…

Android中导入讯飞大模型ai智能系统

1.在讯飞大平台申请免费接口(申请后获取url和token) 2.创建一个数据库进行储存对话聊天记录 package com.example.myapplication.XL; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlit…

【SQL】SQL函数

&#x1f4e2; 前言 函数 是指一段可以直接被另一段程序调用的程序或代码。主要包括了以下4中类型的函数。 字符串函数数值函数日期函数流程函数 &#x1f384; 字符串函数 ⭐ 常用函数 函数 功能 CONCAT(S1,S2,...Sn) 字符串拼接&#xff0c;将S1&#xff0c;S2&#xff0…

Mongodb基础用法【总结】

关系型数据库和非关系型数据库的区别 关系型数据库 1.在关系型数据库中&#xff0c;数据都是存储在表中的&#xff0c;对存储的内容有严格的要求 2.因为我们在创建表的时候久已经规定了表中的字段 存储的数据类型 是否为空 唯一标识等规则 3.由于操作的都是结构化的数据&#…

一款.NET开源的i茅台自动预约小助手

前言 今天大姚给大家分享一款.NET开源、基于WPF实现的i茅台APP接口自动化每日自动预约&#xff08;抢茅台&#xff09;小助手&#xff1a;HyggeImaotai。 项目介绍 该项目通过接口自动化模拟i茅台APP实现每日自动预约茅台酒的功能&#xff0c;软件会在指定时间开始对管理的用…

【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第六篇-阶段总结篇】

因为马上就要进入下一个阶段&#xff0c;制作动态编辑体积纹理的模块。 但在这之前&#xff0c;要在这一章做最后一些整理。 首先&#xff0c;我们完成没完成的部分。其次&#xff0c;最后整理一下图表。最后&#xff0c;本文附上正在用的贴图 完善Shader 还记得我们之前注…