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的动态内存,故该动态对象的引用计数为1
	cout << "指针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_ptr
	unique_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;  // 输出5

    ptr1.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 是否已经被销毁,从而解决了循环引用带来的资源管理问题。

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

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

相关文章

​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进

在当今数字化浪潮愈发汹涌的时代&#xff0c;科技公司的发展不仅需要更强大的计算能力和创新性技术&#xff0c;还需要对环境的高度责任感。在这一背景下&#xff0c;亚马逊云科技的海外服务器产品成为了推动清洁、高效数字未来的领导者之一。亚马逊云科技的高级主管阿比谢克夏…

2024年开通抖店,相关注意事项,新手一定要注意这几个细节

我是王路飞。 马上2024年了&#xff0c;如果你在23年没开通抖店的话&#xff0c;那么2024年就一定不能再错过了。 今天给你们说下&#xff0c;在2024年开通抖店&#xff0c;新手需要注意的相关事项和细节。 内容来源于【醒醒团队-电商王路飞】 首先&#xff0c;开店的时候&a…

Unity中Shader观察空间推导(在Shader中实现)

文章目录 前言一、观察空间矩阵推导1、求观察空间基向量2、求观察空间的基向量在世界空间中的矩阵 的 逆矩阵2、求平移变换矩阵3、相乘得出 观察空间转化矩阵4、得到顶点的世界空间坐标&#xff0c;然后转化到观察空间5、把观察空间坐标转化为齐次裁剪坐标输出到屏幕 二、最终效…

测试开发体系介绍——测试体系介绍-L3

目录&#xff1a; 测试框架体系TDDDDTBDDATDD介绍 测试框架是什么&#xff1f;测试框架的价值&#xff1a;测试框架的收益&#xff1a;常见测试框架类型&#xff1a;TDDBDDBehaviorDrivenDevelopmentATDDAcceptanceTestDrivenDevelopmentMBTModelBasedTestingDDTDataDrivenTes…

HarmonyOS 签名打包Hap(s)、App(s)

1. 基本概念 HarmonyOS应用通过数字证书&#xff08;.cer文件&#xff09;和Profile文件&#xff08;.p7b文件&#xff09;来保证应用的完整性&#xff0c;数字证书和Profile文件可通过申请发布证书和Profile文件获取。   申请数字证书和Profile文件前&#xff0c;首先需要通…

mySQL数据库用户管理

目录 1.创建外键约束 外键的定义 主键表和外键表的理解 具体操作 2.数据库用户管理 新建用户 查看用户信息 重命名用户 删除用户 修改当前和其他用户登录密码 忘记 root密码的解决办法 3.数据库用户授权 授予权限 查看权限 撤销权限 1.创建外键约束 外键的定义…

又是阿里,通义灵码免费平替GitHub Copilot

毫无疑问&#xff0c;人工智能已经在影响着我们日常生活的方方面面&#xff0c;同样的在软件开发领域&#xff0c;AI正在改变我们的开发方式。在软件开发领域&#xff0c;尽管有许多强大的AI编码工具&#xff0c;但国产&#xff0c;免费&#xff0c;使用门槛低&#xff0c;用起…

Fastjson 常用语法

一.Json数据格式回顾 1.1 什么是json JSON:(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript(欧洲计算机协会制定的js规范)的一个子集&#xff0c;采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSO…

省时攻略:快速获得Creo安装包,释放创意天才!

不要再在网上浪费时间寻找Creo的安装包了&#xff0c;一键下载安装&#xff0c; 你要的一切都可以在这里找到&#xff01;我们深知在海量的信息中寻找合适的软件包并非易事&#xff0c;而且往往还伴随着繁琐的安装过程。然而&#xff0c;现在有了我们&#xff0c;一切变得轻松简…

常见激活函数

激活函数是神经网络中的一种非线性变换&#xff0c;它在神经元的输出上引入了非线性性质&#xff0c;使神经网络能够更好地学习和适应复杂的数据模式。以下是一些常见的激活函数&#xff1a; Sigmoid 函数 Sigmoid 函数将输入映射到&#xff08;0&#xff0c;1&#xff09;之间…

模块与包、反序列化校验源码分析、断言、drf之请求、drf之响应

模块与包 什么是模块&#xff1f; 一个py文件&#xff0c;被别的py文件导入使用&#xff0c;它就是模块 如果py文件&#xff0c;直接右键运行&#xff0c;它叫脚本文件 什么是包&#xff1f; 一个文件夹&#xff0c;下有 __init__.py &#xff0c;和很多py文件&#xff0c;这个…

Ubuntu 常用命令之 scp 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 SCP&#xff08;Secure Copy&#xff09;是一种基于SSH&#xff08;Secure Shell&#xff09;的文件传输协议&#xff0c;它可以在本地和远程主机之间安全地复制文件。在Ubuntu系统下&#xff0c;我们可以使用scp命令来实现这个功…

tcp 的限制 (TCP_WRAPPERS)

#江南的江 #每日鸡汤&#xff1a;青春是打开了就合不上的书&#xff0c;人生是踏上了就回不了头的路&#xff0c;爱情是扔出了就收不回的赌注。 #初心和目标&#xff1a;拿到高级网络工程师 TCP_WRAPPERs Tcp_wrappers 对于七层模型中是位于第四层的安全工具&#xff0c;他…

零成本搭建一款博客网站(基于Vercel+Hexo完美实现)【保姆级教程】

文章目录 &#x1f438;基于VercelHexo零成本搭建博客网站&#x1f43b;实现思路 &#x1f42e;Hexo的配置与安装&#x1f412;Hexo的美化与使用&#x1f42b;Github的推送与部署&#x1f43c;Vercel部署与网站上线&#x1f41b;总结 &#x1f438;基于VercelHexo零成本搭建博客…

冬至·特辑:Note4---二叉树的链式结构

目录 前言&#x1f9f6; 1. 二叉树链式结构的实现&#x1f427; 1.1 要实现的目标&#x1f3af; 2.二叉树的创建&#x1f339; 2.1代码实现 2.1.1 TreeNode.h 2.1.2 TreeNode.c 3.实现二叉树的遍历&#x1f432; 3.1 思路分析&#x1f424; 3.2 前/中/后序遍历&#…

IDEA 中 Tomcat 日志乱码

1、服务器输出乱码 修改 File -> settings -> Editor -> General ->Console 中&#xff0c;utf-8改为GBK&#xff0c;反之改成utf-8 2、Tomcat Localhost Log 或者 Tomcat Catalina Log乱码 进入Tomcat 中的conf文件中的logging.properties 哪个有问题改哪个&…

1223西站坐标更新

1223 西站坐标更新 1.Update for the station’s location def initial_out_map_indoor_points(self):Load the indoor data and update both the wall_matrix and the ditch_matrix.# Initialize the wall_matrix# List of coordinatescoordinates [(417, 287, 417, 290),(4…

Java--抽象工厂设计模式

抽象工厂设计模式 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 在抽象工厂模式中&#xff0c;接口是负责…

考研往应届考生报名流程?

文章目录&#xff1a; 一&#xff1a;考试时间相关 二&#xff1a;公告查询获取信息 三&#xff1a;提供材料 1.基本要求 2.证件要求 四&#xff1a;相关问题 1.报名流程如何操作 2.考点选择 2.1 应届考生考点选择 2.2 往届考生考点选择 3.预报名时间可能不同 4.档…

Python编程 圣诞树教程 (附代码)专属于程序员的浪漫

文章目录 1.turtle库2.python函数的定义规则3.引入库4.定义画彩灯函数5.定义画圣诞树的函数6.定义树下面小装饰的函数7.定义一个画雪花的函数8.画五角星9.写文字10.全部源代码11.html圣诞树代码实现&#xff08;动态音乐&#xff09; 1.turtle库 turtle库是Python语言中一个很…