c++动态内存与智能指针

前言

  1. 静态内存:用于保存局部静态变量、类内的静态数据成员以及全局变量
  2. :用于保存函数内部的非static变量
  3. :存储动态分配的对象(程序运行时分配的对象)

静态内存和栈内存的对象由编译器自动创建和销毁

而堆区的动态内存,其生命周期由程序控制

虽然动态内存是必要的,但是管理动态内存是非常棘手的一件事 。

在c++中,动态内存的管理由new/delete这对运算符完成,其中:

  • new负责为对象分配一块内存空间,并返回指向该空间的指针
  • delete负责销毁由new指向的内存空间

虽然c++提供了动态内存的管理机制,但是程序员在使用时很容易忘记释放内存,这样就很容易造成内存泄漏的问题,并且在合适的时间释放内存也是及其困难的问题。

因此,为了更加容易和安全的使用动态内存,c++提供了两种智能指针类管理动态内存对象——shared_ptr与unique_ptr,而weak_ptr是shared_ptr的一种伴随类。

shared_ptr

简介

shared_ptr即共享指针,顾名思义,其允许多个指针指向同一个对象。每当有一个指针指向该共享对象时,该共享对象的引用计数就加1,只有当最后一个指向该对象的 shared_ptr 被销毁时,对象的内存才会被释放。

其实现机制可以简单理解为:普通指针+引用计数,如下图所示,有两个shared_ptr类型的智能指针q和p指向了同一个共享内存对象,则此时p.use_count()与q.use_count()都为2(use_count()方法用于获取该智能指针对所引用对象的引用计数)

初始化

shared_ptr的使用与普通指针的使用基本相同,可以使用*对指针进行解引用

make_shared函数

功能:标准的分配和初始化动态内存

头文件:memory

原型和使用方法

make_shared<T>(args)

  • T:动态对象的类
  • args:动态对象的初始化值
	shared_ptr<string> q = make_shared<string>(10, '.');auto p = make_shared<int>(42);cout << *q << endl;cout << *p << endl;

常用操作

获取shared_ptr中的指针

get()函数

功能:获取shared_ptr的指针

shared_ptr<int> p = make_shared<int>(42);
int *pp = p.get();

 

注意,永远不要用 get 初始化另一个智能指针或者为另一个智能指针赋值。

shared_ptr<int> p(new int(42)); // 引用计数为1
// 新程序块
{// 两个独立的shared_ptr指向相同的内存    shared_ptr<int> q(p.get());   // 错误:将get用来初始化指针指针
}   // 程序块结束,q被销毁,它指向的内存被释放int foo = *p;   // 未定义:p指向的内存已经被释放了

另外,也不要delete get()返回的指针,这可能会导致以下问题:

  1. 多个智能指针共享同一块内存时,如果其中一个智能指针调用了 delete 操作符,会导致其他智能指针持有的原始指针成为悬挂指针,从而引发未定义行为或内存访问错误。

  2. 如果智能指针已经释放了其管理的资源,而你仍然尝试通过 get 返回的指针进行 delete 操作,这将导致二次释放内存,同样会引发未定义行为。

获取shared_ptr所指对象的引用计数

use_count()

功能:获取共享对象的引用计数

	//创建一个动态对象,其值被初始化为42,并使用shared_ptr类型的共享指针p指向该动态内存shared_ptr<int> p = make_shared<int>(42);//由于此时只有一个shared_ptr类型的指针p指向值为42的动态内存,故该动态对象的引用计数为1cout << "指针p所指向动态对象的引用计数:" << p.use_count() << endl;auto q(p);//拷贝动态指针,使得shared_ptr指针q也指向与p所指向的动态内存cout<<"指针q所指向动态对象的引用计数:" << q.use_count() << endl;cout << "指针p所指向动态对象的引用计数:" << p.use_count() << endl;

 

reset()函数

功能:修改shared_ptr所管理的资源,或将shared_ptr置空

shared_ptr<int> p = make_shared<int>(42);cout << "p1:" << *p << endl;
//修改共享指针p的指向为值为24的动态内存,原来的p指向的值为42的动态内存被释放p.reset(new int(24));cout << "p2:" << *p << endl;p.reset();//若参数为空,则表示将共享指针置空

一般情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因此智能指针默认使用 delete 释放它所关联的对象。但是也可以将智能指针绑定到一个指向其他类型的资源的指针上,只是此时需要提供自己的释放操作来替代 delete

// 如果p是唯一指向其对象的shared_ptr,reset会释放此对象。
p.reset();// 若传递了可选的参数内置指针q,会令p指向q,否则会将p置为空。
p.reset(q);//若还传递了参数d,将会调用d而不是delete来释放q
p.reset(q, d);

拷贝与赋值

	shared_ptr<int> p = make_shared<int>(42);auto q(p);//拷贝指针p,让指针p与指针q指向同一块内存
int* p(new int(1024));  // p指向动态内存
auto q = p;             // p和q指向同一块内存
delete p;               // p和q均变为无效
p = nullptr;            // 此时p不再绑定到任何对象
// 此时重置p对q没有任何作用,q仍然指向原来那块(已经被释放的)内存地址
// 在实际系统中,查找指向相同内存的所有指针是异常困难的!

shared_ptr与new的结合使用

接受指针参数的智能指针的初始化函数是explicit的,故而不能将一个普通指针隐式转换为智能指针,必须使用直接初始化方式来初始化一个智能指针,如下:

shared_ptr<int> p1 = new int(1024); // 错误:必须使用直接初始化形式
shared_ptr<int> p2(new int(1024));  // 正确:使用了直接初始化形式

至此,我们已经知道了两种初始化shared_ptr类型指针的方式,分别是使用make_shared函数和使用关键字new进行直接初始化 

同理,一个返回 shared_ptr 的函数也不能在其返回语句中隐式转换一个普通指针:

shared_ptr<int> clone(int p){return new int(p);                  // 错误return shared_ptr<int>(new int(p)); // 正确
}

注意:不要混合使用普通指针和智能指针使用一个内置指针来访问一个智能指针所负责的对象是很危险的,因为我们无法知道对象何时会被销毁

// ptr是传值方式传递,因此拷贝时会递增其引用次数。
void process(shared_ptr<int> ptr){// 使用ptr
}   // ptr离开作用域,ptr会被销毁。由于ptr销毁了,引用次数会递减// process正确的使用方法:
shared_ptr<int> p(new int(42)); // 此时引用计数为1
process(p);     // 拷贝p会递增其引用次数,此时在process中引用计数值为2
int i = *p;     // 离开了process作用域,引用计数值为1 // process错误的使用方法:
int *x(new int(1024));  // 危险:x是普通指针,不是智能指针
process(x);             // 错误:不能将int* 转换为shared_ptr<int>
process(shared_ptr<int>(x)); // 合法,但是由于传参时使用的是一个临时变量,// 临时变量在传递完之后会被销毁,所以传完参// 之后的process里的ptr引用计数为1
int j = *x; // 未定义:离开了process作用域,引用计数值为0,此时x为一个悬空指针

shared_ptr使用案例

#include<iostream>
#include<vector>
#include<string>
#include<memory>
#include<exception>
#include<initializer_list>
using namespace std;typedef vector<string>::size_type size_type;
class StrBlob {
private:shared_ptr<vector<string>> data;void check(const size_type& i, const string& msg);public://构造函数StrBlob();StrBlob(initializer_list<string> li);//使用初始化列表构造函数size_type size();bool empty();void push_back(const string& str);void pop_back();string front();string back();friend ostream& operator<<(ostream& os, const StrBlob& data);
};StrBlob::StrBlob():data(make_shared<vector<string>>()){}
StrBlob::StrBlob(initializer_list<string> li) :data(make_shared<vector<string>>(li)){}void StrBlob::check(const size_type& i,const string& msg)
{if (i >= data->size())throw out_of_range(msg);
}size_type StrBlob::size()
{return data->size();
}bool StrBlob::empty()
{return data->empty();
}void StrBlob::push_back(const string& str)
{data->push_back(str);
}void StrBlob::pop_back()
{check(0, "pop_back on empty StrBlob");data->pop_back();
}string StrBlob::front()
{check(0, "front on empty StrBlob");return data->front();
}
string StrBlob::back()
{check(0, "back on empty StrBlob");return data->back();}ostream& operator<<(ostream& os, const StrBlob& str)
{for (auto p = str.data->cbegin(); p != str.data->cend(); ++p){os << *p << " ";}return os;
}

unique_ptr

  • 与shared_ptr相反,unique_ptr智能指针独占其所指向动态对象的所有权
  • 故unique_ptr不支持拷贝与赋值操作
  • 但是我们可以拷贝一个右值类型unique_ptr,也就是一个即将被销毁的unique_ptr
  • 定义一个 unique_ptr 时,需要将其绑定到一个 new 返回的指针上。初始化 unique_ptr 必须采用直接初始化形式。

	unique_ptr<int> up(new int(42));cout << *up << endl;unique_ptr<int> uq(up);//错误,unique_ptr不支持拷贝操作unique_ptr<int> uq=up;//错误,unique_ptr不支持赋值操作//unique_ptr支持移动操作,也就是将一个即将销毁的对象赋给一个unique_ptrunique_ptr<int> upp = std::move(up);
unique_ptr<int> clone(int s)
{return unique_ptr<int>(new int(s));//return返回的值是一个右值,支持拷贝(移动)
}

weak_ptr

  • weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个 shared_ptr 管理的对象

  • 将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放。即使有 weak_ptr 指向对象,对象也还是会被释放。因此,weak_ptr 的名字抓住了这种智能指针 “弱” 共享对象的特点。

  • 由于对象可能不存在,我们不能使用 weak_ptr 直接访问对象,而必须调用 lock,检查对象是否存在。如果存在,lock 返回一个指向共享对象的 shared_ptr,否则返回一个空 shared_ptr。这使得 weak_ptr 可以用于检测所关联的 shared_ptr 是否已被销毁,从而避免了循环引用问题。

    shared_ptr<int> ptr1 = std::make_shared<int>(5);weak_ptr<int> ptr2 = ptr1;//定义一个weak_ptr,使其指向shared_ptr类型的ptr1所指向的内存对象cout << ptr1.use_count() << endl;//输出1,weak_ptr不占用shared_ptr的引用计数cout << *ptr2.lock() << std::endl;  // 输出5ptr1.reset();//释放ptr1的内存对象if (!ptr2.lock()) {std::cout << "ptr1 has been destroyed." << std::endl;  // 输出ptr1 has been destroyed.}

 

 

weak_ptr的性质可以用来解决shared_ptr的循环引用问题,如下: 

#include <iostream>
#include <memory>class B;  // 前置声明class A {
public:std::shared_ptr<B> bPtr;~A() {std::cout << "A destructor called." << std::endl;}
};class B {
public:std::weak_ptr<A> aWeakPtr;~B() {std::cout << "B destructor called." << std::endl;}
};int main() {std::shared_ptr<A> aPtr = std::make_shared<A>();std::shared_ptr<B> bPtr = std::make_shared<B>();aPtr->bPtr = bPtr;bPtr->aWeakPtr = aPtr;return 0;
}

在这个示例中,我们定义了两个类 AB,它们之间存在循环引用关系。A 类包含一个 shared_ptr<B> 成员变量,而 B 类包含一个 weak_ptr<A> 成员变量。

main() 函数中,我们创建了 A 类和 B 类的实例,并将它们互相引用。这样就形成了一个循环引用结构,如果不采取措施,这些对象将永远无法被销毁,导致内存泄漏。

但是,由于 B 类使用了 weak_ptr 来引用 A 类的对象,weak_ptr 不会增加引用计数。当 shared_ptr 指向 A 类的对象被销毁时,A 类的析构函数会被调用,并且与之关联的 B 类对象的 weak_ptr 将自动失效。

因此,在这个示例中,循环引用问题得到了解决,AB 对象都能够正常销毁,并在其析构函数中输出相应的消息。

总之,通过使用 weak_ptr,我们可以打破 shared_ptr 的循环引用,避免内存泄漏问题。weak_ptr 允许我们检测所关联的 shared_ptr 是否已经被销毁,从而解决了循环引用带来的资源管理问题。

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

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

相关文章

分布式搜索elasticsearch概念

什么是elasticsearch&#xff1f; elasticsearch是一款非常强大的开源搜索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 目录 elasticsearch的场景 elasticsearch的发展 Lucene篇 Elasticsearch篇 elasticsearch的安装 elasticsearch的场景 elasticsear…

显示器屏幕oled的性能、使用场景、维护

OLED显示器屏幕具有许多独特的性能和使用场景&#xff0c;以下是关于OLED显示器屏幕的性能、使用场景和维护的详细介绍&#xff1a; 一、性能 色彩鲜艳&#xff1a;OLED显示器屏幕能够呈现出更加鲜艳的色彩&#xff0c;色彩饱和度高&#xff0c;色彩还原性好&#xff0c;可以给…

lv12 根文件系统12

目录 1 根文件系统 2 BusyBox 3 实验九 3.1 在 busybox 官网下载 busybox 源码&#xff08;这里我们下载 busybox-1.22.1.tar.bz2&#xff09; 3.2 拷贝 busybox 源码包到 ubuntu 的家目录下&#xff0c;解压并进入其顶层目录 3.3 进入 busybox 配置界面&#xff08;…

【Midjourney】Midjourney根据prompt提示词生成黑白色图片

目录 &#x1f347;&#x1f347;Midjourney是什么&#xff1f; &#x1f349;&#x1f349;Midjourney怎么用&#xff1f; &#x1f514;&#x1f514;提示词格式 &#x1f34b;&#x1f34b;应用示例——“秘密花园”式涂色书配图生成 &#x1f34c;&#x1f34c;例子1…

SpringMVC系列之技术点定向爆破二

SpringMVC的运行流程 客户端发送请求 tomcat接收对应的请求 SpringMVC的核心调度器DispatcherServlet接收到所有请求 请求地址与RequestMapping注解进行匹配&#xff0c;定位到具体的类和具体的处理方法&#xff08;封装在Handler中&#xff09; 核心调度器找到Handler后交…

【Java 基础】33 JDBC

文章目录 1. 数据库连接1&#xff09;加载驱动2&#xff09;建立连接 2. 常见操作1&#xff09;创建表2&#xff09;插入数据3&#xff09;查询数据4&#xff09;使用 PreparedStatement5&#xff09;事务管理 3. 注意事项总结 Java Database Connectivity&#xff08;JDBC&…

rqt_graph使用说明

其中右边的&#xff1a;/rosout是一个topic 也就是一个话题 /rosout是一个topic 也是一个话题 可以看到凡是在rqt_graph里面用长方形标识的全都是话题 通过观察可以发现&#xff1a;凡是用椭圆标识的全都是节点 如果切换为Nodes only视图会发现&#xff1a; 所说的no…

Java 中的内部类的定义

目录 一、成员内部类 二、静态内部类 三、局部内部类 四、匿名内部类 一、成员内部类 public class InnerClass {String name;private Integer age;static String hobby;/*** 成员内部类* 1、成员内部类中只能定义非静态属性和方法* 2、成员内部类中可以访问外部类的成员&a…

十一.约束(一)

约束 1.约束(constraint)概念1.1为什么需要约束1.2什么是约束1.3约束的分类 2.非空约束2.1作用2.2关键字2.3特点2.4添加非空约束2.5删除非空约束 3.唯一性约束3.1作用3.2关键字3.3特点3.4添加唯一约束3.5关于复合唯一约束3.5删除唯一约束 4.PRIMARY KEY 约束4.1作用4.2关键字4.…

数据分析基础之《numpy(5)—合并与分割》

了解即可&#xff0c;用panads 一、作用 实现数据的切分和合并&#xff0c;将数据进行切分合并处理 二、合并 1、numpy.hstack 水平拼接 # hstack 水平拼接 a np.array((1,2,3)) b np.array((2,3,4)) np.hstack((a, b))a np.array([[1], [2], [3]]) b np.array([[2], […

循环渲染ForEach

目录 1、接口说明 2、键值生成规则 3、组件创建规则 3.1、首次渲染 3.2、非首次渲染 4、使用场景 4.1、数据源不变 4.2、数据源组项发生变化 4.3、数据源数组项子属性变化 5、反例 5.1、渲染结果非预期 5.2、渲染性能降低 Android开发中我们有ListView组件、GridVi…

Apache ShenYu 网关JWT认证绕过漏洞 CVE-2021-37580

Apache ShenYu 网关JWT认证绕过漏洞 CVE-2021-37580 已亲自复现 漏洞名称漏洞描述影响版本 漏洞复现环境搭建漏洞利用 修复建议总结 Apache ShenYu 网关JWT认证绕过漏洞 CVE-2021-37580 已亲自复现) 漏洞名称 漏洞描述 Apache ShenYu是一个异步的&#xff0c;高性能的&#x…

Qt中字符串转换为JS的函数执行

简介 在 QML 中&#xff0c;将 JavaScript 字符串转换为函数通常涉及使用 Function 构造函数或 eval() 函数。但是&#xff0c;QML 的环境对 JavaScript 的支持有一定的限制&#xff0c;因此不是所有的 JavaScript 功能都可以在 QML 中直接使用。 以下介绍都是在Qt5.12.1…

Mybatis3系列课程8-带参数查询

简介 上节课内容中讲解了查询全部, 不需要带条件查, 这节我们讲讲 带条件查询 目标 1. 带一个条件查询-基本数据类型 2.带两个条件查询-连个基本数据类型 3.带一个对象类型查询 为了实现目标, 我们要实现 按照主键 查询某个学生信息, 按照姓名和年级编号查询学生信息 按照学生…

【【迭代七次的CORDIC算法-Verilog实现】】

迭代七次的CORDIC算法-Verilog实现求解正弦余弦函数 COEDIC.v module CORDIC #(parameter DATA_WIDTH 4d8 , // we set data widthparameter PIPELINE 4d8)(input clk ,input …

Jmeter关联操作

1.首先右键添加一个线程选择线程组,命名为线程组-1&#xff0c;添加取样器选择HTTP请求--城市天气 2.线程组-1右键&#xff0c;添加取样器选择后置处理器中的JSON提取器 3.线程组-1右键,添加取样器选择后置处理器中的BeanShell 后置处理程序(必须平级) 4.首先右键添加一个线程选…

[C++ 从入门到精通] 17.基类与派生类关系的详细再探讨

&#x1f4e2;博客主页&#xff1a;https://loewen.blog.csdn.net&#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;本文由 丶布布原创&#xff0c;首发于 CSDN&#xff0c;转载注明出处&#x1f649;&#x1f4e2;现…

微电网优化(Matlab复现)— 微电网两阶段鲁棒优化经济调度方法_刘一欣

论文链接&#xff1a;微电网两阶段鲁棒优化经济调度方法 - 中国知网 代码链接&#xff1a;https://m.tb.cn/h.5Mg7fCo?tkhnpmWgZiv2R 复现效果&#xff1a; 运行环境&#xff1a;Matlab 2020bCplexyalmip 1 微电网结构 图 1 所示为典型的微电网结构&#xff0c;由可控分布式…

如何在Portainer部署一个web站点到Nginx容器并结合内网穿透远程访问

文章目录 前言1. 安装Portainer1.1 访问Portainer Web界面 2. 使用Portainer创建Nginx容器3. 将Web静态站点实现公网访问4. 配置Web站点公网访问地址4.1公网访问Web站点 5. 固定Web静态站点公网地址6. 固定公网地址访问Web静态站点 前言 Portainer是一个开源的Docker轻量级可视…

基于Vite+Vue3 给项目引入Axios

基于ViteVue3 给项目引入Axios,方便与后端进行通信。 系列文章指路&#x1f449; 系列文章-基于Vue3创建前端项目并引入、配置常用的库和工具类 文章目录 安装依赖新建src/config/config.js 用于存放常用配置进行简单封装解决跨域问题调用尝试 安装依赖 npm install axios …