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();//不一块内存,递减count
        this->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解除指针循环引用

思维导图

在这里插入图片描述

本模块思路

在这里插入图片描述

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

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

相关文章

上午面了个腾讯拿 38K 出来的,让我见识到了基础的天花板

今年的校招基本已经进入大规模的开奖季了&#xff0c;很多小伙伴收获不错&#xff0c;拿到了心仪的 offer。 各大论坛和社区里也看见不少小伙伴慷慨地分享了常见的面试题和八股文&#xff0c;为此咱这里也统一做一次大整理和大归类&#xff0c;这也算是划重点了。 俗话说得好…

WIN10下解决HIVE 初始化MYSQL表报错:Unknown version specified for initialization

今天本地WINDOWS装HIVE&#xff0c;走到最后一步初始化数据库死活不通过&#xff1a; D:\hive\hive-rel-release-3.1.3\bin\ext>hive --service schematool -dbType mysql -initSchema --verbose SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found bind…

【机器视觉技术栈】03 - 镜头

镜头 定焦镜头变焦镜头远心镜头 FA镜头与远心镜头的区别&#xff1f; 焦距越小畸变程度越大&#xff0c;精度要求不高的场景可以使用焦距大的FA镜头做尺寸测量&#xff0c;但焦距越大带来的问题就是整个机械设备越大。精度高的场景使用远心镜头进行尺寸测量。 光学基础知识…

Android画布Canvas绘制drawBitmap基于源Rect和目的Rect,Kotlin

Android画布Canvas绘制drawBitmap基于源Rect和目的Rect&#xff0c;Kotlin <?xml version"1.0" encoding"utf-8"?> <androidx.appcompat.widget.LinearLayoutCompat xmlns:android"http://schemas.android.com/apk/res/android"xmlns…

如何将微服务注册到eureka-server中

将需要注册到eureka-server的服务的maven的pom文件中添加eureka-client依赖 <!--eureka-client依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>&l…

AIGC发展史

1 AIGC概况 1.1 AIGC定义 AIGC&#xff08;AI Generated Content&#xff09;是指利用人工智能技术生成的内容。它也被认为是继PGC,UGC之后的新型内容生产方式&#xff0c;AI绘画、AI写作等都属于AIGC的具体形式。2022年AIGC发展速度惊人&#xff0c;迭代速度更是呈现指数级发…

MYSQL练题笔记-高级查询和连接-连续出现的数字

一、题目相关内容 1&#xff09;相关的表和题目 2&#xff09;帮助理解题目的示例&#xff0c;提供返回结果的格式 二、自己初步的理解 其实这一部分的题目很简单&#xff0c;但是没啥思路啊&#xff0c;怎么想都想不通&#xff0c;还是看题解吧&#xff0c;中等题就是中等题…

HarmonyOS与AbilitySlice路由配置

上一章我有教到鸿蒙应用开发——Ability鸿蒙应用开发的基础知识&#xff0c;那么今天我们来讲一下AbilitySlice路由配置 AbilitySlice路由配置 虽然一个Page可以包含多个AbilitySlice&#xff0c;但是Page进入前台时界面默认只展示一个AbilitySlice。默认展示的AbilitySlice是…

我最喜欢的白版应用,AI加持的新功能开源!强烈推荐

Excalidraw 把他们的文本到图表的功能开源了 Excalidraw是一个虚拟白板应用&#xff0c;专门用于绘制类似手绘的图表。它提供了一个无限的、基于画布的白板&#xff0c;具有手绘风格&#xff0c;支持多种功能。 之前我分享的&#xff1a;72张PNG&#xff0c;图解机器学习 里面…

对 MyBatis Plus SaveBatch 调优提升25倍性能!!!

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、源码分析 二、优化过程 三、疑问 四、总结 前言 最近在压测一批接口&#xff0c;发现接口处理速度慢的有点超出预期&#x…

软件兼容性测试有哪些好处?专业CMA、CNAS软件测评中心推荐

软件兼容性测试是保证软件在不同操作系统、不同浏览器、不同设备上正常运行的重要环节。一款兼容性良好的软件能在不同的硬件和软件环境中展现出稳定、高效的性能&#xff0c;用户体验也会更加顺畅&#xff0c;主要有以下好处&#xff1a; 1、减少软件上线后的故障和问题&…

C //例10.1 从键盘输入一些字符,逐个把它们送到磁盘上去,直到用户输入一个“#”为止。

C程序设计 &#xff08;第四版&#xff09; 谭浩强 例10.1 例10.1 从键盘输入一些字符&#xff0c;逐个把它们送到磁盘上去&#xff0c;直到用户输入一个“#”为止。 IDE工具&#xff1a;VS2010 Note: 使用不同的IDE工具可能有部分差异。 代码块 方法&#xff1a;使用指针&…

Java-宋红康-(P133-P134)-多线程创建方式(Thread and Runnable)

b站视频 133-多线程-线程创建方式1&#xff1a;继承Thread类_哔哩哔哩_bilibili 目录 3.1 继承Thread 3.1.1 继承Thread类方式 3.1.2 线程的执行流程 3.1.3 线程内存图 3.1.4 run()方法和start()方法 3.1.5 线程名字的设置和获取 3.1.6 获取运行main方法线程的名字 3.…

【Java Web学习笔记】5 - XML

项目代码 https://github.com/yinhai1114/JavaWeb_LearningCode/tree/main/xml 零、在线文档 XML系列教程 一、XML引出 1.为什么需要XML 1.需求1 :两个程序间进行数据通信? 2.需求2:给一台服务器&#xff0c;做-一个配置文件&#xff0c;当服务器程序启动时&#xff0c;去…

【AntDB 数据库】国产数据库发展之信创政策的加持

由于我国在信息技术领域起步较晚&#xff0c;国内大量的市场份额被国际IT巨头占据&#xff0c;甚至长期处于被垄断的地位&#xff0c;这也给了某些国家妄图通过挑起科技、贸易摩擦制衡我国发展的机会。为了解决可能存在的安全风险&#xff0c;在重要信息系统、关键基础设施中使…

linux-进程退出

进程终止 进程终止进程终止原因 进程返回值echo $?exit()和_exit() 父进程获取进程退出码wait()方法waitpid()方法获取子进程的退出码&#xff08;status&#xff09; 进程的非阻塞等待(WNOHANG)问题 进程终止 进程终止原因 进程终止原因有三种情况。 代码运行完毕&#xf…

【多线程】-- 12 线程协作之生产者消费者问题及解决办法

多线程 9 线程协作 “生产者消费者问题” ——并非二十三种设计模式之一 9.1 生产者消费者问题 “线程通信” 应用场景&#xff1a;生产者和消费者问题 假设仓库中只能存放一件产品&#xff0c;生产者将生产出来的产品放入仓库&#xff0c;消费者将仓库中产品取走消费如果…

linux 服务的JDK安装

1.jdk安装 1.1 jdk下载 官网下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads/ 这里我使用的是rpm的方式&#xff0c;所以下载rpm的包。如下图&#xff1a;1.2 jdk的安装 下载完成后&#xff0c;会有一个以rpm结尾的包&#xff0c;例如&#xff1a;jd…

【恋上数据结构】哈夫曼树学习笔记

哈夫曼树 哈夫曼编码&#xff08;Huffman Coding&#xff09; 哈夫曼编码&#xff0c;又称为霍夫曼编码&#xff0c;它是现代压缩算法的基础 假设要把字符串 [ABBBCCCCCCCCDDDDDDEE] 转成二进制编码进行传输。 可以转成 ASCII 编码 (6569&#xff0c;10000011000101) &…

牛客算法题【HJ96 表示数字】golang实现

题目 HJ96 表示数字 golang实现 package mainimport ("fmt""unicode" )func main() {s : ""var s_o stringvar char_pre, r runefor {n, _ : fmt.Scan(&s)if n 0 {break} else {for _, r range s {if unicode.IsDigit(r) {if !unicode.…