C++(智能指针)

智能指针

1.基础:

1.1 概念

智能指针是用于自动管理动态分配内存的RAII(Resource Acquisition Is Initialization)对象。它们通过自动释放内存来防止内存泄漏,是替代裸指针的安全工具。

1.2 解析

众所周知,堆内存对象 需要 手动 使用 delete 销毁 如果 没有 使用 delete 销毁 就会 造成 内存 泄漏
所以C + + 在ISO9 8 标准 引入了 智能 指针 概念 I S O 1 1 趋于 完善

智能指针可以堆内存对象具有栈内存对象特点原理需要手动回收内内存对象套上一个栈内存的模板类对象即可

如下图,智能指针类似把堆区指针对象放到新的指针类中进行管理,这样就可以和栈区对象一样,所处的{}结束就会自动释放内存,不用手动delete。

2.分类:

自动指针:auto_ptr        (C++ISO98,已被废除

唯一指针:unique_ptr    (C++IOS11)

共享指针:shared_ptr    (C++IOS11)

虚指针:weak_ptr          (C++IOS11)

2.1 自动指针

(1)使用:
#include <iostream>
#include <memory>using namespace std;class Animal{
private:string breed;string name;
public:Animal(string breed,string name):breed(breed),name(name){cout << this->breed << ":构造函数" << endl;}~Animal(){cout << breed << ":析构函数" << endl;}void print(){cout << "物种:" << breed << endl;cout << "名字:" << name << endl;}
};int main (){{Animal *a = new Animal("狗","mike");auto_ptr<Animal> ap1(a);                //自动指针管理堆区指针对象aap1.get()->print();                     //使用get函数调用管理的指针对象//ap1.release();                        //直接放弃对已有指针对象a的管理,不会释放堆区空间,如果不delete对象a可能导致内存泄露//ap1.reset();                          //放弃对已有指针对象a的管理,同时释放堆区空间/**1. 放弃对原有指针的管理,同时释放空间,*2. 再创建新的堆区对象*3. ap1指针对新的堆区指针对象进行管理*/ap1.reset(new Animal("猫","lisa"));ap1.get()->print();                     //打印新管理的对象的成员变量cout << "程序块运行结束" << endl;}cout << "程序运行结束" << endl;return 0;
}

(2)问题(被废除原因):

由于成员变量存在指针类型因此拷贝构造函数赋值运算符重载使用出现问题浅拷贝不同auto_ptr复制语义会造成资源控制权转移问题

#include <iostream>
#include <memory>using namespace std;class Animal{
private:string breed;string name;
public:Animal(string breed,string name):breed(breed),name(name){cout << this->breed << ":构造函数" << endl;}~Animal(){cout << breed << ":析构函数" << endl;}void print(){cout << "物种:" << breed << endl;cout << "名字:" << name << endl;}
};int main (){{Animal *a = new Animal("狗","mike");auto_ptr<Animal> ap1(a);                //自动指针管理堆区指针对象aauto_ptr<Animal> ap2(ap1);              //显示调用拷贝函数cout << ap1.get() << " " << ap2.get() << endl;auto_ptr<Animal> ap3 = ap2;             //隐式调用拷贝函数cout << ap1.get() << " " << ap2.get() << " " << ap3.get() << endl;auto_ptr<Animal> ap4;ap4 = ap3;                              //调用的是重载的赋值运算符cout << ap1.get() << " " << ap2.get() << " " << ap3.get() << " " << ap4.get() << endl;cout << "程序块运行结束" << endl;}cout << "程序运行结束" << endl;return 0;
}

2.2 唯一指针

(1)特点:

1.和auto_ptr基本一致,唯一的不同就是unique_ptr指针对资源对象有唯一控制权,不能使用常规语法直接赋值和调用拷贝构造函数拷贝资源对象的控制权。

2.如果想让别的unique_ptr指针抢夺资源控制权就使用move(ap1)。

(2)使用:
#include <iostream>
#include <memory>using namespace std;class Animal{
private:string breed;string name;
public:Animal(string breed,string name):breed(breed),name(name){cout << this->breed << ":构造函数" << endl;}~Animal(){cout << breed << ":析构函数" << endl;}void print(){cout << "物种:" << breed << endl;cout << "名字:" << name << endl;}
};int main (){{Animal *a = new Animal("狗","mike");unique_ptr<Animal> up1(a);                //自动指针管理堆区指针对象aup1.get()->print();//unique_ptr<Animal> up2(up1);                  //不使用move不能转移控制权,下面两种方式一样unique_ptr<Animal> up2(move(up1));              //显示调用拷贝函数cout << up1.get() << " " << up2.get() << endl;unique_ptr<Animal> up3 = move(up2);             //隐式调用拷贝函数cout << up1.get() << " " << up2.get() << " " << up3.get() << endl;unique_ptr<Animal> up4;up4 = move(up3);                              //调用的是重载的赋值运算符cout << up1.get() << " " << up2.get() << " " << up3.get() << " " << up4.get() << endl;cout << "程序块运行结束" << endl;}cout << "程序运行结束" << endl;return 0;
}

2.3 共享指针

(1)特点:

1.对一个堆区对象进行管理,解决了抢夺控制权转换的问题,可以多个共享指针一起管理一个堆区对象。

2.新增一个引用计数,对一起管理同一个对象的指针计数,每多一个共享指针管理这个堆区对象,引用计数加一。

3.当管理这个堆区空间的所有共享指针都被释放,才会回收这个堆区对象。 

4.shared_ptr有两种创建方式,一种和之前的创建方式一样。第二种常用,是其他智能指针没有的,使用:shared_ptr<类型> sp1 = make_shared<类型>(初始化内容)。

(2)使用:
#include <iostream>
#include <memory>using namespace std;class Animal{
private:string breed;string name;
public:Animal(string breed,string name):breed(breed),name(name){cout << this->breed << ":构造函数" << endl;}~Animal(){cout << breed << ":析构函数" << endl;}void print(){cout << "物种:" << breed << endl;cout << "名字:" << name << endl;}
};int main (){shared_ptr<Animal> sp3;{//shared_ptr<Animal> sp1(new Animal("狗","mike"));              //先创建堆区对象,再让智能 指针引用管理堆区对象,速度慢shared_ptr<Animal> sp1 = make_shared<Animal>("狗","mike");       //直接创建智能指针并且引用对象,一步到位sp1.get()->print();cout << sp1.get() << endl;cout << "引用计数:" << sp1.use_count() << endl;shared_ptr<Animal> sp2 = sp1;               //隐式调用拷贝构造函数cout << sp1.get() << " " << sp2.get() << endl;cout << "引用计数:" << sp1.use_count() << endl;sp3 = sp2;              //赋值运算符重载cout << sp1.get() << " " << sp2.get() << " " << sp3.get() << endl;cout << "引用计数:" << sp1.use_count() << endl;cout << "程序块运行结束" << endl;}cout << "引用计数:" << sp3.use_count() << endl;         //程序块结束,只剩下ap3管理对象cout << "程序运行结束" << endl;return 0;
}

2.4 虚指针

虚指针weak_ptr是用来观查共享指针所管理的资源,像一个旁观者,来记录观查的对象(堆区对象)是否被shared_ptr管理,有几个share_ptr在管理。

(1)特点:

1.虚指针weak_ptr是一个不控制资源对象管理的智能指针,不会影响资源的引用计数,主要的目的是协助share_ptr工作

2.通过weak_ptr的构造函数,参数传入一个持有资源对象的share_ptr或者weak_ptr的指针

3.weak_ptr与资源呈弱相关性,不能单独对资源进行控制,不可以调用get()函数操作资源

4.weak_ptr可以调用.lock函数让shared_ptr指针获得一个持有资源的share_ptr,在调用.lock函数前要先检测weak_ptr的引用计数是否大于零(观察的资源是否还有shared_ptr指针还在管理,如果没有,对应的资源也不存在)。

#include <iostream>
#include <memory>using namespace std;class Animal{
private:string breed;string name;
public:Animal(string breed,string name):breed(breed),name(name){cout << this->breed << ":构造函数" << endl;}~Animal(){cout << breed << ":析构函数" << endl;}void print(){cout << "物种:" << breed << endl;cout << "名字:" << name << endl;}
};int main (){weak_ptr<Animal> wp2;shared_ptr<Animal> sp3;{shared_ptr<Animal> sp1 = make_shared<Animal>("狗","mike");       //直接创建智能指针并且引用对象,一步到位sp1.get()->print();                            //打印weak_ptr<Animal> wp1(sp1);                  //使用虚函数wp1来观测共享指针对堆区对象引用次数,不会增加引用计数cout << "引用计数:" << sp1.use_count() << endl;cout << "引用计数:" << wp1.use_count() << endl;shared_ptr<Animal> sp2 = sp1;               //隐式调用拷贝构造函数//sp1.reset();                              //删除当前共享指针,如果还有其他共享指针管理就不释放所管理的资源wp2 = sp2;                                  //wp2也观测堆区对象cout << "引用计数:" << sp1.use_count() << " " << wp2.use_count() << endl;sp3 = sp2;cout << "程序块运行结束" << endl;}//即使观测的共享指针被释放,wp2依旧存在,wp1被释放cout << "引用计数:" << wp2.use_count() << endl;         //程序块结束,虚函数观测管理资源对象的引用计数--0cout << "程序运行结束" << endl;return 0;
}

3.手写一个共享指针Share_ptr类

  • 构造函数
  • 拷贝构造函数
  • 赋值运算符
  • get函数
  • use_count函数
  • reset函数
  • 析构函数

代码实现:

#include <iostream>
#include <memory>using namespace std;template <class T>
class Shareptr{
private:T *res = nullptr;int *count = nullptr;
public:Shareptr(){}Shareptr(T *s):res(s),count(new int(1)){}       //构造函数Shareptr(const Shareptr &sp):res(sp.res),count(sp.count){ //拷贝构造函数(*count)++;}T* get()const{                      //调用管理的资源return res;}int use_count()const{return *count;}void reset(){                   //删除当前共享指针,如果还有其他共享指针管理资源对象,不删除资源对象if(count != nullptr && res != nullptr){       //如果当前共享指针不是空(*count)--;if((*count) == 0){delete res;delete count;}res = nullptr;count = nullptr;}}Shareptr &operator =(const Shareptr &sp){       //赋值运算符重载if(&sp != this){reset();res = sp.res;count = sp.count;(*count)++;}return *this;}~Shareptr(){reset();}
};class Animal{
private:string breed;string name;
public:Animal(string breed,string name):breed(breed),name(name){cout << this->breed << ":构造函数" << endl;}~Animal(){cout << breed << ":析构函数" << endl;}void print(){cout << "物种:" << breed << endl;cout << "名字:" << name << endl;}
};int main (){Shareptr<Animal> sp3;{//Shareptr<Animal> sp1 = make_shared<Animal>("金渐层","lisa");//Shareptr<Animal> sp1 = new Animal("金渐层","lisa");      //隐式调用构造函数Shareptr<Animal> sp1(new Animal("金渐层","lisa"));       //显示调用构造函数sp1.get()->print();cout << sp1.use_count() << " " << sp1.get() << endl;Shareptr<Animal> sp2 = sp1;                              //调用拷贝构造函数cout << sp1.use_count() << " " << sp1.get() << endl;cout << sp2.use_count() << " " << sp2.get() << endl;sp3 = sp1;                                               //调用重载的赋值运算符cout << sp1.use_count() << " " << sp1.get() << endl;cout << sp2.use_count() << " " << sp2.get() << endl;cout << sp3.use_count() << " " << sp3.get() << endl;}cout << "程序结束" << endl;return 0;
}

扩展:shared_ptr 可能产生的内存泄露

问题描述:

当两个或多个对象通过 shared_ptr 互相持有对方时,会形成循环引用(也叫“环状引用”)。这样即使它们都不再被外部引用,由于它们内部的 shared_ptr 还在互相引用,引用计数永远不会归零,导致内存无法释放,产生内存泄漏。

错误示例:

#include <iostream>
#include <memory>
using namespace std;class Car; // 前向声明class Person {
public:shared_ptr<Car> car;~Person() { cout << "Person destroyed" << endl; }
};class Car {
public:shared_ptr<Person> owner;~Car() { cout << "Car destroyed" << endl; }
};int main() {auto p = make_shared<Person>();auto c = make_shared<Car>();p->car = c;c->owner = p;// main 结束后,Person 和 Car 都不会被销毁
}

运行后不会输出 A destroyed 和 B destroyed,因为它们的引用计数始终大于0。

解决办法

用 weak_ptr 打破循环引用。

weak_ptr 是一种不增加引用计数的智能指针。通常在一方用 shared_ptr,另一方用 weak_ptr。

修改后:

#include <iostream>
#include <memory>
using namespace std;class Car; // 前向声明class Person {
public:shared_ptr<Car> car;~Person() { cout << "Person destroyed" << endl; }
};class Car {
public:weak_ptr<Person> owner; // 用 weak_ptr~Car() { cout << "Car destroyed" << endl; }
};int main() {auto p = make_shared<Person>();auto c = make_shared<Car>();p->car = c;c->owner = p; // 这里是 weak_ptr,不增加引用计数// main 结束后,Person 和 Car 都会被正确销毁
}

这样,A 和 B 都会被正确释放,输出:

总结

循环引用会导致 shared_ptr 管理的对象无法释放,造成内存泄漏。

用 weak_ptr 替代其中一方的 shared_ptr,即可打破循环,避免泄漏。

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

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

相关文章

Sentinel 授权规则详解与自定义异常处理

Sentinel 授权规则详解与自定义异常处理 在微服务系统中&#xff0c;权限控制和访问保护是至关重要的一环。本文将详细介绍如何通过 Sentinel 的 授权规则&#xff08;AuthorityRule&#xff09; 控制资源访问权限&#xff0c;并结合实际案例说明如何设置白名单与黑名单&#…

LeetCode Hot 100 最大子数组和

给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组是数组中的一个连续部分。 示例 1&#xff1a; 输入&#xff1a;nums [-2,1,-3,4,-1,2,1,-5,4] 输出&#xff1a;6…

Python UI自动化演进格局:从传统库到AI驱动的智能代理

引言 UI自动化的持久需求 在现代软件工程和业务流程管理中&#xff0c;图形用户界面&#xff08;GUI&#xff09;自动化扮演着至关重要的角色。它不仅仅局限于软件测试领域&#xff0c;更是机器人流程自动化&#xff08;RPA&#xff09;、自动化数据录入、遗留系统集成以及在AP…

【Java面试】如何解决MQ死信队列?

如何解决MQ死信队列&#xff1f; 一、预防死信产生&#xff08;从源头减少死信&#xff09; 消费者端健壮性优化 捕获所有可能的异常&#xff0c;区分可恢复异常&#xff08;如网络超时&#xff09;和不可恢复异常&#xff08;如数据格式错误&#xff09;。对可恢复异常实现自…

RGB+EVS视觉融合相机:事件相机的革命性突破​

一、单一EVS事件相机的原理 事件相机&#xff08;EVS&#xff09;是一种新型的视觉传感器&#xff0c;其设计灵感来源于生物视觉系统。与传统相机不同&#xff0c;事件相机并不以固定的帧率捕获整个图像&#xff0c;而是每个像素独立工作&#xff0c;当检测到亮度变化超过预设…

DBeaver 设置阿里云中央仓库地址的操作步骤

DBeaver 设置阿里云中央仓库地址的操作步骤&#xff08;适用于解决驱动下载缓慢或失败的问题&#xff09; 一、最新阿里云 Maven 仓库地址 主仓库地址&#xff08;推荐&#xff09;&#xff1a; http://maven.aliyun.com/nexus/content/groups/public/ 123 备用地址&#xff…

Qt:QCustomPlot库的QCPAxis

在 QCustomPlot 中&#xff0c;QCPAxis 是图表坐标系的核心组件&#xff0c;负责管理坐标轴的所有视觉和功能特性。它提供了丰富的定制选项&#xff0c;使开发者能够创建高度专业化的数据可视化图表。 核心功能概述 功能类别关键特性相关方法基本结构坐标轴位置、方向axisTyp…

七天学会SpringCloud分布式微服务——05——OpenFeign

1、OpenFeign实现远程调用 1.1 services.pom引入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>1.2 在service-order微服务中新建feign.Product…

大语言模型的通用局限性与全球技术演进

基于行业最新数据修订&#xff08;2025Q2&#xff09; 一、知识截止期&#xff1a;全球模型的进化差异 所有LLM都存在​​知识截止期&#xff08;Knowledge Cut-off&#xff09;​​&#xff0c;即模型训练数据的时间上限。这在技术迭代飞快的软件开发领域尤为致命——2023年后…

常见网络安全威胁和防御措施

网络安全威胁是一种技术风险&#xff0c;会削弱企业网络的防御能力&#xff0c;危及专有数据、关键应用程序和整个 IT 基础设施。由于企业面临广泛的威胁&#xff0c;因此他们应该仔细监控和缓解最关键的威胁和漏洞。网络安全问题有七大类&#xff0c;它们都包括多种威胁&#…

人工智能和云计算对金融未来的影响

你有没有想过&#xff0c;你的钱是否会由人工智能而不是银行来管理&#xff1f;如果你的银行不存在于真实的地方&#xff0c;而是存在于几千公里之外的某台大型超级计算机上&#xff0c;那会怎样&#xff1f;这可能有一天会发生&#xff0c;让我们看看它是如何发生的&#xff0…

Vue3——项目配置eslint+prettier

一、安装依赖 pnpm add -D eslint prettier eslint-plugin-vue vue-eslint-parser typescript-eslint/eslint-plugin typescript-eslint/parser eslint-config-prettier eslint-plugin-prettier typescript-eslint二、创建或修改 eslint.config.cjs // eslint.config.cjs con…

人工智能编程三大核心流程详解--机器学习、神经网络、NLP自然语言处理

对于学习人工智能阶段&#xff0c;代码可以写出来&#xff0c;主要是按照构建流程一步一步&#xff0c;所以本篇博客主要是通过三个大点来介绍&#xff1a;第一个点是机器学习中预测损失值与真实值之间的误差流程&#xff1b;第二点是深度学习中神经网络搭建流程&#xff1b;第…

《AI for Science:深度学习如何重构基础科学的发现范式?》

前言 前些天发现了一个巨牛的人工智能免费学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站 AI for Science&#xff1a;深度学习如何重构基础科学的发现范式&#xff1f; 副标题&#xff1a;从费曼图到神经微分方程&…

创客匠人:创始人 IP 打造引领知识变现新路径​

在当下知识经济蓬勃发展的时代&#xff0c;知识变现已成为众多创作者和从业者关注的焦点。创客匠人作为行业内的重要参与者&#xff0c;为创始人 IP 打造与知识变现提供了独特且有效的思路。​ 创始人 IP 打造在知识变现中占据着关键地位。创客匠人认为&#xff0c;一个成功的…

JVM调优实战 Day 7:JVM线程分析与死锁排查

【JVM调优实战 Day 7】JVM线程分析与死锁排查 文章标签 jvm调优, 线程分析, 死锁排查, JVM监控, Java性能优化, JVM参数配置 文章简述 在Java应用的高并发场景中&#xff0c;线程管理与死锁问题往往是性能瓶颈的根源。本文作为“JVM调优实战”系列的第7天&#xff0c;深入解析…

Kotlin中协程挂起函数的本质

一、核心概念&#xff1a;挂起函数的本质 1. 核心定义 挂起函数&#xff08;Suspending Function&#xff09;是 Kotlin 协程的核心机制&#xff0c;它允许函数在执行过程中暂停&#xff08;挂起&#xff09;而不阻塞线程&#xff0c;并在条件满足时恢复执行。 2. 与普通函数…

人工智能中的集成学习:从原理到实战

大家好&#xff01;今天我们来聊聊人工智能领域中一个非常强大的技术——集成学习&#xff08;Ensemble Learning&#xff09;&#x1f60e;。——这个让模型预测能力飙升的“团队合作”神器&#xff01;无论你是刚入门的新手还是想复习的老司机&#xff0c;这篇通俗教程都能帮…

大事件项目记录13-登录优化-redis

一、redis优化登录接口。 原有代码中在修改密码在产生新令牌后并未将旧的令牌主动失效&#xff0c;旧的令牌依然可以使用 &#xff0c;会产生安全隐患&#xff0c;所以需要对其进行优化。 1.令牌主动失效机制。 &#xff08;1&#xff09;登录成功后&#xff0c;给浏览器响应令…

重塑音视频叙事:Premiere文本剪辑与Podcast AI降噪的革命性工作流

一、 开篇的另一些心里话 最近淘到个好东西&#xff0c;是来自奥地利Blueskyy艺术学院的Adobe教育版授权&#xff0c;深度体验下来&#xff0c;感觉就像是给我的创意工具箱做了一次“满配”升级&#xff0c;有些心得不吐不快&#xff0c;必须跟同路的设计师朋友们碰一碰。 在分…