动态内存基础(二)

news2024/11/17 15:37:44

智能指针
● 使用 new 与 delete 的问题:内存所有权不清晰,容易产生不销毁,多销毁的情况

int* fun()
{
    int* res = new int(100); //fun()拥有对fun()申请的内存的销毁权
    return res;
}
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    int* y = fun(); //main()也拥有对fun()申请的内存的销毁权

    return a.exec();
}
int* fun()
{
    static int res = 100; //fun()拥有对fun()申请的内存的销毁权
    return &res;
}
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    int* y = fun(); //main()可能无权销毁fun()中的静态数据

    return a.exec();
}

● C++ 的解决方案:智能指针
– auto_ptr ( C++17 删除)
– shared_ptr / uniuqe_ptr / weak_ptr
● shared_ptr——基于引用计数的共享内存解决方案
– 基本用法

#include<memory> //包含头文件memory
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    std::shared_ptr<int> x(new int(3)); //声明等价于int* x(new int(3))
    std::cout << *x << std::endl;

    return a.exec();
}

在这里插入图片描述

#include<memory>
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    std::shared_ptr<int> x(new int(3)); //通过引用计数(同一时刻该内存的使用者数量)来决定什么时候销毁,不会产生内存泄漏。此时引用计数为1
    std::cout << x.use_count() << std::endl; //输出1
    std::shared_ptr<int> y = x; //OK,不会产生内存泄漏。与x共享引用计数对象,此时引用计数为2
    std::cout << y.use_count() << std::endl; //输出2
    std::cout << x.use_count() << std::endl; //输出2
    //程序结束先销毁y,调用y的析构函数,引用计数变为1,再销毁x,调用x的析构函数,引用计数变为0,调用delete
    
    return a.exec();
}

在这里插入图片描述

#include<memory>
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    std::shared_ptr<int> x(new int(3));
    std::cout << x.use_count() << std::endl; //输出1
    {
        std::shared_ptr<int> y = x;
        std::cout << y.use_count() << std::endl; //输出2
        //因为在语句体当中,语句结束销毁y
    }
    std::cout << x.use_count() << std::endl; //输出1

    return a.exec();
}

在这里插入图片描述

#include<memory>
std::shared_ptr<int> fun()
{
    std::shared_ptr<int> res(new int(100));
    std::cout << res.use_count() << std::endl; //输出1
    return res;
}
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    auto y = fun();
    std::cout << y.use_count() << std::endl; //输出1
    //主函数结束,引用计数变为1,调用delete

    return a.exec();
}

在这里插入图片描述

– reset / get 方法
std::shared_ptr::reset
std::shared_ptr::get

#include<memory>
std::shared_ptr<int> fun()
{
    std::shared_ptr<int> res(new int(100));
    return res;
}

void fun2(int*)
{
    
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    auto y = fun();
    std::cout << *y << std::endl; //y的类型std::shared_ptr<int>
    std::cout << *(y.get())<< std::endl; //y.get()的类型为int*。get() 返回存储的指针,而非被管理指针。
    
    fun2(y); //类型不匹配
    fun2(y.get()); //兼容C风格指针,支持相互调用
    return a.exec();
}
#include<memory>
std::shared_ptr<int> fun()
{
    std::shared_ptr<int> res(new int(100));
    return res;
}

void fun2(int*)
{

}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    auto y = fun();
    y.reset(new int(3)); //释放原先的内存,关联到新内存
    y.reset((int*)nullptr); //等价于std::shared_ptr<int> z;。nullptr不能隐式转换为int*,要加上(int*)

    return a.exec();
}

– 指定内存回收逻辑
std::shared_ptr::shared_ptr:template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );

#include<memory>
void fun(int* ptr)
{
	std::cout << "void fun(int* ptr)" << std::endl;
	delete ptr;
}
int main(int argc, char *argv[])
{
	std::shared_ptr<int> res(new int(3), fun);

}

在这里插入图片描述

void dummy(int*)
{
	std::cout << "void dummy(int*)\n";
}
std::shared_ptr<int> fun()
{
	static int res = 3;
	return std::shared_ptr<int>(&res, dummy);
}
int main(int argc, char *argv[])
{
	auto y = fun();
	return 0;
}

在这里插入图片描述

– std::make_shared

int main(int argc, char *argv[])
{
	std::shared_ptr<int> ptr(new int(3)); //ptr保存了申请的动态内存的地址和引用计数(也在堆上)的地址
	std::shared_ptr<int> ptr2 = ptr; //OK,希望和ptr共享同一个引用计数
	auto ptr3 = std::make_shared<int>(3); //OK,构造了动态内存和引用计数,make_shared引入了优化,使得这两块内存距离足够近,属于同一条缓存线,可以同时被读入缓存,只需一次访存,就可以完成引用计数-1并调用delete,提升了性能

	return 0;
}

– 支持数组( C++17 支持 shared_ptr<T[]> ; C++20 支持 make_shared 分配数组)

#include<memory>
int main(int argc, char *argv[])
{
	//Before C++17, 指定内存回收逻辑,释放对象数组
	std::shared_ptr<int[]> ptr(new int[5]); //Since C++17
	auto ptr2 = std::make_shared<int[]>(5); //Since C++20, 等价于std::shared_ptr<int[]> ptr2 = std::make_shared<int[]>(5);

	return 0;
}

– 注意: shared_ptr 管理的对象不要调用 delete 销毁

#include<memory>
int main(int argc, char *argv[])
{
	std::shared_ptr<int> m(new int(3)); //shared_ptr已经包含delete m.get();
	delete m.get(); //Error: double free detected

	std::shared_ptr<int> x(new int(3)); //shared_ptr已经包含delete x.get();
	std::shared_ptr<int> y(x); //OK,x的内存信息和引用计数信息都传递给y
	std::shared_ptr<int> y(x.get()); //Error: double free detected,系统认为y拥有这块内存的拥有权,并将引用计数置为1,销毁y的时候,引用计数-1并释放内存,销毁x的时候又是引用计数-1并释放内存,导致错误

	return 0;
}

● unique_ptr——独占内存的解决方案
– 基本用法
– unique_ptr 不支持复制,但可以移动

#include<iostream>
#include<new>
#include<memory>
int main(int argc, char *argv[])
{
	std::unique_ptr<int> x(new int(3)); //x独享new int(3)占用的内存
	std::cout << x.get() << '\n';
	//std::unique_ptr<int> y = x; //Error
	std::unique_ptr<int> z = std::move(x); //OK
	std::cout << x.get() << '\n'; //x的内容已经移动到z里了
	std::cout << z.get() << '\n'; //打印原先x独享的内存
	return 0;
}

在这里插入图片描述

#include<iostream>
#include<new>
#include<memory>
std::unique_ptr<int> fun()
{
	//std::unique_ptr<int> res(new int(3));
	auto res = std::make_unique<int>(3); //等价于上面的语句
	return res;
}
int main(int argc, char *argv[])
{
	std::unique_ptr<int> x = fun();
	std::cout << x.get() << std::endl;
	return 0;
}

在这里插入图片描述

– 为 unique_ptr 指定内存回收逻辑

#include<iostream>
#include<new>
#include<memory>
void fun(int* ptr)
{
	std::cout << "void fun()\n";
	delete ptr;
}
int main(int argc, char *argv[])
{
	//std::unique_ptr<int> x(new int(3), fun); //fun与unique_ptr的第二个缺省实参类型不匹配
	std::unique_ptr<int,decltype(&fun)> x(new int(3), fun); //使用decltype(&fun);让编译器自动推导其类型
	std::cout << x.get() << std::endl;
	return 0;
}

在这里插入图片描述

● weak_ptr——防止循环引用而引入的智能指针

struct Str
{
	std::shared_ptr<Str> m_mei;
	~Str()
	{
		std::cout << "Str::~Str()\n";
	}
};
int main(int argc, char *argv[])
{
	std::shared_ptr<Str> x(new Str{}); //x包含的内存的引用计数为1
	std::shared_ptr<Str> y(new Str{}); //y包含的内存的引用计数为1
	x->m_mei = y; //y的引用计数由1变为2,因为x也引用了y包含的内存
	y->m_mei = x;//x的引用计数由1变为2,因为y也引用了x包含的内存
	//程序运行到这里y(x也是一样的)的引用计数由2变为1,不调用析构函数
	//程序结束之后并没有打印Str::~Str(),说明构造的对象并没有被销毁
	return 0;
}

在这里插入图片描述

struct Str
{
	std::shared_ptr<Str> m_mei;
	~Str()
	{
		std::cout << "Str::~Str()\n";
	}
};
int main(int argc, char *argv[])
{
	std::shared_ptr<Str> x(new Str{});
	std::shared_ptr<Str> y(new Str{});
	x->m_mei = y;
	//y->m_mei = x; //如果注掉这句话就OK
	return 0;
}

在这里插入图片描述

#include<iostream>
#include<new>
#include<memory>
struct Str
{
	std::shared_ptr<Str> m_mei;
	~Str()
	{
		std::cout << "Str::~Str()\n";
	}
};
int main(int argc, char *argv[])
{
	std::shared_ptr<Str> x(new Str{});
	//std::shared_ptr<Str> y(new Str{});
	x->m_mei = x; //自己给自己赋值也会造成循环引用
	return 0;
}

在这里插入图片描述

– 基于 shared_ptr 构造

#include<iostream>
#include<new>
#include<memory>
struct Str
{
	std::weak_ptr<Str> m_mei;
	~Str()
	{
		std::cout << "Str::~Str()\n";
	}
};
int main(int argc, char *argv[])
{
	std::shared_ptr<Str> x(new Str{}); //weak_ptr可以和shared_ptr共享一块内存,但weak_ptr在构造的时候并不会增加shared_ptr的引用计数
	std::shared_ptr<Str> y(new Str{});
	x->m_mei = y; //可以使用一个shared_ptr初始化一个weak_ptr,初始化后并不会增加y的引用计数的值
	y->m_mei = x; //x和y内的引用计数都是1
	return 0;
}

在这里插入图片描述

– lock 方法

struct Str
{
	std::weak_ptr<Str> m_mei;
	~Str()
	{
		std::cout << "Str::~Str()\n";
	}
};
int main(int argc, char *argv[])
{
	std::shared_ptr<Str> x(new Str{});
	std::shared_ptr<Str> y(new Str{});
	x->m_mei = y;
	y->m_mei = x;

	auto ptr = x->m_mei.lock(); //ptr指向一个有效的shared_ptr
	if (ptr)
	{
		std::cout << "Can access pointer\n";
	}
	else
	{
		std::cout << "Cannot access pointer\n";
	}
	return 0;
}

在这里插入图片描述
参考
深蓝学院:C++基础与深度解析

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

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

相关文章

Java线程池-重点类源码解析--更新中

1.Runnable和Callable的区别 (1) Callable规定&#xff08;重写&#xff09;的方法是call()&#xff0c;Runnable规定&#xff08;重写&#xff09;的方法是run() (2) Callable的任务执行后可返回值&#xff0c;而Runnable的任务是不能返回值的 (3) call方法可以抛出异常&#…

总时差与自由时差

定义总时差&#xff08;总浮动时间&#xff09;&#xff08;TF&#xff0c;Total Free Time&#xff0c;不耽误项目总进度&#xff09;LS&#xff08;Latest Start&#xff09;-ES&#xff08;Earliest Start&#xff09;LF&#xff08;Latest Finish&#xff09;-EF&#xff0…

SpringCloud:Nacos配置管理

目录 一、统一配置管理 1、打开nacos控制台&#xff0c;点击配置管理&#xff0c;添加配置 2、编写配置 3、完成示例 ​二、服务配置拉取 1、引入Nacos配置管理客户端依赖 2、在resource目录添加一个bootstrap.yml文件&#xff0c;这个文件是引导文件&#xff0c;优先级高…

English Learning - L2 第 3 次小组纠音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.3.4 周六

English Learning - L2 第 3 次小组纠音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.3.4 周六共性问题小元音 [ʌ]小元音 [ɒ]小元音 [ʊ]小元音 [ɪ]小元音 [ə]小元音 [e]我的发音问题纠音过程共性问题 小元音 [ʌ] 口型容易偏大 解决办法&#xff1a;因为嘴角没有放松&#xff0c…

基于java的俱乐部会员管理系统

技术&#xff1a;Java、JSP等摘要&#xff1a;随着科学技术的飞速发展&#xff0c;科学技术在人们日常生活中的应用日益广泛&#xff0c;也给各行业带来发展的机遇&#xff0c;促使各个行业给人们提供更加优质的服务&#xff0c;有效提升各行业的管理水平。俱乐部通过使用一定的…

程序员画流程图的工具Draw.io

Draw.io 是一个很好用的免费流程图绘制工具,制图结果本质上是xml文件&#xff0c;web版和桌面版可以支持导出图像&#xff08;png或者svg矢量图都可以&#xff09;。你可以利用它绘制一系列的图表、图示或图形&#xff0c;包括流程图、UML类图、组织结构图、泳道图、E-R 图、文…

人脸网格/人脸3D重建 face_mesh(毕业设计+代码)

概述 Face Mesh是一个解决方案&#xff0c;可在移动设备上实时估计468个3D面部地标。它利用机器学习&#xff08;ML&#xff09;推断3D面部表面&#xff0c;只需要单个摄像头输入&#xff0c;无需专用深度传感器。利用轻量级模型架构以及整个管道中的GPU加速&#xff0c;该解决…

Python QT5设计UI界面教程

简介&#xff1a;PyQT5开发常用知识&#xff0c;零基础上手&#xff0c;需配合我之前写的博文&#xff0c;配置好QT设计工具和ui文件转py文件的工具。博文为&#xff1a;使用Python PyQt5实现一个简单的图像识别软件&#xff1b;页面效果如下&#xff1a; 1.设计菜单栏 Contai…

[数据结构与算法(严蔚敏 C语言第二版)]第1章 绪论(学习复习笔记)

1.1 数据结构的研究内容 计算机解决问题的步骤 从具体问题抽象出数学模型设计一个解此数学模型的算法编写程序&#xff0c;进行测试、调试&#xff0c;直到解决问题 计算机解决问题的过程中寻求数学模型的实质是 分析问题&#xff0c;从中提取操作的对象&#xff0c;并找出这些…

【iOS】Blocks

BlockBlocks概要什么是Blocks&#xff1f;Block语法Block类型变量截获自动变量值__block说明符Blocks的实现Block的实质Blocks概要 什么是Blocks&#xff1f; Blocks可简单概括为&#xff1a; 带有自动变量&#xff08;局部变量&#xff09;的匿名函数 在使用Blocks时&#x…

socket 编程实战(编写服务器程序 )

IP 地址格式转换函数 对于人来说&#xff0c;我们更容易阅读的是点分十进制的 IP 地址&#xff0c;譬如192.168. 1.110 、192.168.1.50&#xff0c;这其实是一种字符串的形式&#xff0c;但是计算机所需要理解的是二进制形式的 IP 地址&#xff0c;所以我们就需要在点分十进制…

代码随想录第三章读书笔记——数组

一.二分查找前提&#xff1a;数组为有序数组&#xff0c;数组中无重复元素&#xff0c;因为一旦有重复元素&#xff0c;使用二分查找法返回的元素下标可能不是唯一的&#xff0c;这些都是使用二分法的前提条件&#xff0c;当题目描述满足如上条件的时候&#xff0c;可要想一想是…

FPGA的GigE Vision IP相机图像采集方案设计,转换为千兆UDP,支持10G MAC

1 概述 GigE Vision是一个比较复杂的协议&#xff0c;要在FPGA中完全实现具有较大的难度。如果FPGA作为接收端希望实现GigE Vision相机的配置和图像采集功能&#xff0c;则只需要实现其中小部分功能即可。本文对原有GigE Vision协议的结构进行了裁剪&#xff0c;仅保留设备搜索…

Maven基本使用以及IDEA中配置使用的详细介绍

文章目录MavenMaven基本介绍Maven基本使用IDEA配置MavenIDEA配置MavenMaven坐标详解IDEA创建Maven项目IDEA导入Maven项目Maven依赖管理Maven Maven基本介绍 Apache Maven 是一个项目管理和构建工具&#xff0c;它基于项目对象模型(POM)的概念&#xff0c;通过一小段描述信息来…

GAMES101学习笔记——光栅化

一&#xff1a;什么是光栅化&#xff08;Rasterization&#xff09; 把空间里的物体画在屏幕上。 屏幕由一个个阵列排布的像素点组成&#xff0c;屏幕大小指宽度方向由width个像素点&#xff0c;高度方向由height个像素点。 像素点索引范围&#xff1a;&#xff08;0&#xf…

【Java开发】JUC进阶 03:读写锁、阻塞队列、同步队列

1 读写锁&#xff08;ReadWriteLock&#xff09;&#x1f4cc; 要点实现类&#xff1a;ReentrantReadWirteLock通过读写锁实现更细粒度的控制&#xff0c;当然通过Synchronized和Lock锁也能达到目的&#xff0c;不过他们会在写入和读取操作都给加锁&#xff0c;影响性能&#x…

黑马程序员SSM框架教程之学习笔记1

P44-SpringMVC入门案例 1.在pom.xml中导入坐标springMVC与servlet 2.创建一个SpringMVC控制器类 3.创建springMVC配置文件springMvcCponfig 4.定义一个servlet容器启动配置类&#xff0c;在里面加载spring的配置 5.在pom.xml文件中配置tomcat插件 运行结果显示 P45-springMVC入…

2023-03-05:ffmpeg推送本地视频至lal流媒体服务器(以RTMP为例),请用go语言编写。

2023-03-05&#xff1a;ffmpeg推送本地视频至lal流媒体服务器&#xff08;以RTMP为例&#xff09;&#xff0c;请用go语言编写。 答案2023-03-05&#xff1a; 使用 github.com/moonfdd/ffmpeg-go 库。 先启动lal流媒体服务器软件&#xff0c;然后再执行命令&#xff1a; go…

linux通过nginx映射指定目录文件给外部访问

修改配置文件 #user www-data; #将user www-data;注掉改为root user root; worker_processes auto; pid /run/nginx.pid; include /etc/nginx/modules-enabled/*.conf;events {worker_connections 768;# multi_accept on; }http {### Basic Settings##sendfile on;tcp_nopush …

【C语言学习笔记】:三子棋具体步骤和代码

一、问题描述 用c语言实现三子棋。 二、基本流程 在写三子棋的代码之前&#xff0c;我们来看看实现这个游戏的逻辑&#xff1a; 1.菜单界面选择开始或者退出游戏。2.创建棋盘并初始化。3.打印棋盘。4.玩家落子(玩家输入行列坐标的方式来落子)&#xff0c;x’表示玩家落子。5…