C++智能指针及简单实现

C++智能指针

  • 堆内存、栈内存与静态内存
    • 静态内存
    • 栈内存
    • 堆内存
  • 动态内存管理
    • new、delete运算符
    • 智能指针
    • 实现智能指针
  • shared_ptr
    • 智能指针的线程安全问题
    • 解决
  • unique_ptr
  • weak_ptr
    • 循环引用
  • 思维导图
  • 本模块思路

动态内存管理 - cppreference.com

堆内存、栈内存与静态内存

静态内存

  • **保存局部static对象。**例如统计函数本身被调用了多少次,可以在函数体内定义一个static对象,不会随函数体结束而销毁,更特别的是,只会在第一次使用时初始化。
  • 保存类的static成员。类的static成员仅与类有关,而非与类的每个对象关联,更重要的是,static成员的更新会应用到类的每个对象。
  • 全局变量

栈内存

  • 函数内的非静态对象

堆内存

  • 保存动态分配的对象

动态内存管理

new、delete运算符

  • new 在申请内存的同时,还会调用对象的构造函数,返回指向该对象的指针
  • delete 在释放内存之前,会调用对象的析构函数
  • 易产生问题:内存泄漏(忘记释放)、引用非法内存指针(释放早了)

智能指针

位于memory头文件,智能指针是模板类(类似vector),所以相当于把指针包成一个类,添加一些成员(use_count等)的同时使得指针拥有了构造函数和析构函数,这样我们只需要关注内存的申请,内存的释放则由程序自动完成。

下面先手动实现一个智能指针:

实现智能指针

smartptr.h

#pragma once
#ifndef SMART_PTR_H
#define SMART_PTR_H
#include<iostream>template<typename T>
class SmartPtr
{
public://默认构造函数SmartPtr() :ptr(nullptr), count(nullptr) {}//传指针构造函数SmartPtr(T* _ptr) : ptr(_ptr), count(nullptr) {if (_ptr)count = new int(1);}//拷贝构造函数SmartPtr(const SmartPtr& smp){ptr = smp.ptr;count = smp.count;if (count)(*count)++;}//析构函数~SmartPtr(){reset();}//重载=运算符SmartPtr& operator=(const SmartPtr& smp){if (this == &smp)//指向同一块共享内存{return *this;}reset();//不一块内存,递减countthis->ptr = smp.ptr;this->count = smp.count;if (count)(*count)++;return *this;}//重载*运算符T operator*() {return *(this->ptr);}//重载->运算符T* operator->() {return this->ptr;}//取出原始指针T* get(){return this->ptr;}//检查是否只有一个共享指针bool unique(){return *count == 1;}//返回计数器int use_count(){return *count;}//析构时削减共享计数并检查void reset(){if (count){(*count)--;if (*count == 0){delete this->ptr;delete this->count;}}}private:T* ptr;int* count;
};#endif // !SMART_PTR_H

main.cpp

#include<iostream>
#include<memory>
#include"smartptr.h"using std::make_shared;
using std::shared_ptr;
using std::cout;
using std::endl;int main()
{//对比int *sp = 100;auto sp = make_shared<int>(100);auto mysp = SmartPtr<int>(new int(100));cout << "shared_ptr:" << *sp << endl;cout << "My shared_ptr:" << *mysp << endl;cout << "shared_ptr use_count:" << sp.use_count() << endl;cout << "My shared_ptr use_count:" << mysp.use_count() << endl;cout << "shared_ptr unqiue:" << sp.unique() << endl;cout << "My shared_ptr unique:" << mysp.unique() << endl;auto sp2 = shared_ptr<int>(sp);auto mysp2 = SmartPtr<int>(mysp);cout << "shared_ptr use_count:" << sp.use_count() << endl;cout << "My shared_ptr use_count:" << mysp.use_count() << endl;cout << "shared_ptr unqiue:" << sp.unique() << endl;cout << "My shared_ptr unique:" << mysp.unique() << endl;auto p = sp.get();auto myp = mysp.get();auto sp3 = make_shared<int>();auto mysp3 = SmartPtr<int>();sp3 = sp2;mysp3 = mysp2;cout << "shared_ptr use_count:" << sp.use_count() << endl;cout << "My shared_ptr use_count:" << mysp.use_count() << endl;cout << "shared_ptr unqiue:" << sp.unique() << endl;cout << "My shared_ptr unique:" << mysp.unique() << endl;auto sp4 = make_shared<int>(10);auto mysp4 = SmartPtr<int>(new int(10));mysp3 = mysp4;sp3 = sp4;cout << "shared_ptr use_count:" << sp.use_count() << endl;cout << "My shared_ptr use_count:" << mysp.use_count() << endl;cout << "shared_ptr unqiue:" << sp.unique() << endl;cout << "My shared_ptr unique:" << mysp.unique() << endl;getchar();return 0;
}

shared_ptr

在这里插入图片描述
智能指针会将new和delete的过程自动化,它本质上是一个原始指针的包装。
一个允许多个对象指向同一块内存的指针对象,会对该内存地址的引用数量进行计数,shared ptr中除了有一个指针,指向所管理数据的地址。还有一个指针指向一个控制块的地址,里面存放了所管理数据的数量 (常说的引用计数) 、weak ptr的数量、删除器、分配器等,这里控制块是线程安全的,但是所管理数据的地址不是,
在这里插入图片描述

智能指针的线程安全问题

多个线程同时修改同一个shared_ptr对象,线程不安全

两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,原因可看i++是原子操作吗,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了,具体可以看C++ 智能指针线程安全的问题中的例子。

所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何context switch (切换到另一个线程)。 通常所说的原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。

解决

加锁,但是更好的方法是尽量不要多线程地改变指针指向

也可以用原子智能指针:C++ 20 引入了原子智能指针std::atomic<std::shared_ptr>

unique_ptr

unique_ptr 不共享它所管理的对象,两个unique_ptr不能指向同一个对象,unique”占有“它的指针,不能赋值和拷贝,智能释放指针或是对控制权进行转移。

使用时,先include
在这里插入图片描述
这样我们就定义了一个名为entity的智能指针,一个更好的出于防止构造函数抛出异常的定义是:
在这里插入图片描述

weak_ptr

使用weak_ptr复制shared_ptr时不会增加引用计数,这是它最大的特点
这种指针不具有指针功能,最大作用在于解决循环引用的问题

循环引用

可以理解为形成了循环链表,A要释放就要先释放指向A的B,B要释放就要先释放指向B的A,这种时候会造成内存泄漏,此时将其中一个改成weak_ptr,然后在需要获取操作权的时候使用weak_ptr.lock()返回指向共享内存空间的shared_ptr()即可,详见c++ weak ptr解除指针循环引用

思维导图

在这里插入图片描述

本模块思路

在这里插入图片描述

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

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

相关文章

视觉测量基础

1. 相机模型 1.1 坐标系转换原理 世界坐标系(world Coords):点在真实世界中的位置&#xff0c;描述相机位置。 相机坐标系(Cameras Coords):以相机光学系统中心&#xff08;镜头中心&#xff09;为原点&#xff0c;建立相机坐标系。 图像物理坐标系(Film Coords):经过小孔成…

微服务实战系列之J2Cache

前言 经过近几天陆续发布Cache系列博文&#xff0c;博主已对业界主流的缓存工具进行了基本介绍&#xff0c;当然也提到了一些基本技巧。相信各位盆友看见这么多Cache工具后&#xff0c;在选型上一定存在某些偏爱: A同学说&#xff1a;不管业务千变万化&#xff0c;我对Redis的…

企业如何制定精准营销策略?

在当今的数字化时代&#xff0c;位置数据已经成为企业营销策略中不可或缺的一部分。通过收集和分析客户的位置数据&#xff0c;企业可以更好地了解客户的行为和需求&#xff0c;制定更精准的营销策略&#xff0c;从而提高营销效率。 首先&#xff0c;利用IP地址位置数据可以帮助…

手搓图片滑动验证码_JavaScript进阶

手搓图片滑动验证码 背景代码效果图展示网站 背景 在做前端项目开发的时候&#xff0c;少不了登录注册部分&#xff0c;既然有登录注册就少不了机器人验证&#xff0c;验证的方法有很多种&#xff0c;比如短信验证码、邮箱验证码、图片滑动、图片验证码等。 由于鄙人在开发中…

9个Logo素材超多的Logo网站!

Logo 虽然看起来很简单&#xff0c;但是设计过程中的每一个细节都很精致。因为 Logo 作为品牌的象征&#xff0c;应该一目了然地传达给人们品牌的理念和形象。本文给大家整理了 7 个 Logo 素材网站和 2 个 Logo 在线制作网站。可以收集很多关 Logo 设计的内容和技巧&#xff01…

吉他初学者学习网站搭建系列(5)——如何做一个在线节拍器

文章目录 背景实现TransportLoop代码 在线尝试 背景 我们看吉他谱时&#xff0c;经常看到拍号&#xff0c;例如6/8。它的含义是一拍是一个八分音符&#xff0c;一小节有六拍。四分音符的时长是一秒&#xff0c;即60拍/分钟。基于这样的背景知识&#xff0c;我们就可以根据一些…

supervisor管理python进程

前言 平时开发调试中使用conda环境&#xff0c;项目比较多环境多&#xff0c;而且命令繁杂&#xff0c;每一次启动项目都可能会因为忘记启动方式而频繁报错。现在可以通过supervisor来管理&#xff0c;只需要配置几个文件&#xff0c;就可以轻松通过简单一致的命令启动工程&…

C++ day55 判断子序列 不同的子序列

题目1&#xff1a;392 判断子序列 题目链接&#xff1a;判断子序列 对题目的理解 判断字符串s是否为t的子序列 字符串s和字符串t的长度大于等于0&#xff0c;字符串s的长度小于等于字符串t的长度&#xff0c;本题其实和最长公共子序列的那道题很相似&#xff0c;相当于找两…

HashMap相关专题

前置知识&#xff1a;异或运算 异或运算介绍 异或有什么神奇之处&#xff08;应用&#xff09;&#xff1f; &#xff08;1&#xff09;快速比较两个值 &#xff08;2&#xff09;我们可以使用异或来使某些特定的位翻转&#xff0c;因为不管是0或者是1与1做异或将得到原值的相…

IntelliJ IDEA 2023.2新特性详解第三弹!Docker、Kubernetes等支持!

9 Docker 在 Docker 镜像层内预览文件 现在可以在 Services&#xff08;服务&#xff09;工具窗口中轻松访问和预览 Docker 镜像层的内容。 从列表选择镜像&#xff0c;选择 Show layers&#xff08;显示层&#xff09;&#xff0c;然后点击 Analyze image for more informati…

<软考>软件设计师-2操作系统(总结)

(一) 进程管理 1 操作系统概述 1-1 操作系统定义: 能有效地组织和管理系统中的各种软/硬件资源&#xff0c;合理地组织计算机系统工作流程&#xff0c;控制程序的执行&#xff0c;并且向用户提供一个良好的工作环境和友好的接口。 1-2 操作系统的作用: 1 通过资源管理提高计…

7+WGCNA+机器学习+实验+泛癌分析,多要素干湿结合

今天给同学们分享一篇生信文章“Analysis and Experimental Validation of Rheumatoid Arthritis Innate Immunity Gene CYFIP2 and Pan-Cancer”&#xff0c;这篇文章发表在Front Immunol期刊上&#xff0c;影响因子为7.3。 结果解读&#xff1a; DEG筛选和数据预处理 数据在…

Helplook VS Google Docs:一对一比较

还记得Google Docs在2006年一炮走红的时候吗&#xff1f;它很大程度地改变了协作方式&#xff0c;也减少了附加文件和频繁保存的麻烦。相比Microsoft Word&#xff0c;很多人更喜欢Google Docs的简单性。 但是时代也在不断地发展。像HelpLook这样的新竞争对手也可以提供先进的…

字符集——带你了解UTF-8的前世今生

文章目录 字符集的来历汉字和字母的编码特点Unicode字符集字符集小结编码和解码开发约定 字符集的来历 计算机是美国人发明的&#xff0c;由于计算机能够处理的数据只能是0和1组成的二进制数据&#xff0c;为了让计算机能够处理字符&#xff0c;于是美国人就把他们会用到的每一…

前端面试高频考点—事件循环Event loop

目录 事件循环 执行步骤 概念讲解 主线程 微任务(micro task) 宏任务(macro task) Event Loop经典例题 这段代码的执行结果是什么&#xff1f; 正确答案&#xff1a; 具体流程&#xff1a; 事件循环 主线程从"任务队列"中读取执行事件&#xff0c;这个过程…

File类—递归文件搜索执行脚本文件

文章目录 一、需求分析二、File类2.1 File对象的创建2.2 File判断和获取方法2.3 创建和删除方法2.4 遍历文件夹方法 三、Runtime类—常见api四、递归文件搜索执行脚本文件 一、需求分析 在本篇博客中&#xff0c;我们想通过递归文件的方式&#xff0c;在D:\\判断下搜索QQ.exe这…

麒麟V10安装kerberos客户端

麒麟V10系统安装kerberos客户端 当系统具备yum镜像源的时候需要执行安装命令 yum install krb5-devel krb5-client krb5-libs -y 会提示报错 “未找到匹配的参数:krb5-client” 此时我们需要手动安装krb5-client 安装包链接放到了这里 链接: https://pan.baidu.com/s/1x1YVr6…

SQL Server的安装和首个库的创建

一、熟悉SQL Server的安装环境&#xff1b; 1.安装Microsoft的数据库管理系统SQL Server 2022 先把SQL Server 2022下载好后进行解压后出现以下界面然后点击基本进行安装 然后会出现以下界面&#xff1a; 一步步按照提示往下走即可&#xff0c;把SQL Server 2022安装完成后再…

Leetcode刷题笔记题解(C++):LCR 021. 删除链表的倒数第 N 个结点

思路&#xff1a;用双指针去遍历链表&#xff0c;删除left的下一个节点&#xff0c;注意的是n大于等于链表长度即删除第一个节点 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {…

tmux简单使用

它允许你在一个终端窗口中创建多个终端会话&#xff0c;并在它们之间进行切换。以下是tmux的一些主要用途和功能&#xff1a; 多窗口&#xff1a; Tmux允许你在一个终端中创建多个窗口。每个窗口可以包含一个或多个终端会话&#xff0c;你可以轻松地在这些窗口之间切换。面板分…