C++ 智能指针

1.why?

⾸先,说⼀下为什么要使⽤智能指针:智能指针其作⽤是管理⼀个指针,避免咋们程序员申请的空间在函数结束时忘记释放,造成内存泄漏这种情况滴发⽣。
然后使⽤智能指针可以很⼤程度上的避免这个问题,因为智能指针就是⼀个类,当超出了类的作⽤域是,类会⾃动调⽤析构函数,析构函数会⾃动释放资源。所以智能指针的作⽤原理就是在函数结束时⾃动释放内存空间,不需要⼿动释放内存空间。
 

内存泄漏举例

#include <iostream>
#include <string>
#include <memory>using namespace std;// 动态分配内存,没有释放就return
void memoryLeak1() {string *str = new string("动态分配内存!");// new 出来的是指针return;
}// 动态分配内存,虽然有些释放内存的代码,但是被半路截胡return了
int memoryLeak2() {string *str = new string("内存泄露!");// ...此处省略一万行代码// 发生某些异常,需要结束函数if (1) {return -1;}/// 另外,使用try、catch结束函数,也会造成内存泄漏!/delete str;	// 虽然写了释放内存的代码,但是遭到函数中段返回,使得指针没有得到释放return 1;
}int main(void) {memoryLeak1();memoryLeak2();return 0;
} 

2.what?

unique_ptr 

unique_ptr特性

  1. 基于排他所有权模式:两个指针不能指向同一个资源
  2. 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
  3. 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
  4. 在容器中保存指针是安全的
// 1 禁止复制构造和=赋值unique_ptr<string> p1(new string("I'm Li Ming!"));
unique_ptr<string> p2(new string("I'm age 22."));cout << "p1:" << p1.get() << endl;// 获取原生指针,即一个地址
cout << "p2:" << p2.get() << endl;p1 = p2;					// 禁止左值赋值
unique_ptr<string> p3(p2);	// 禁止左值赋值构造unique_ptr<string> p3(std::move(p1));
// 可以进行所有权转移
p1 = std::move(p2);	// 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样cout << "p1 = p2 赋值后:" << endl;
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;// 2 在容器里面不允许直接赋值vector<unique_ptr<string>> vec;
unique_ptr<string> p3(new string("I'm P3"));
unique_ptr<string> p4(new string("I'm P4"));vec.push_back(std::move(p3));
vec.push_back(std::move(p4));cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;vec[0] = vec[1];	/* 不允许直接赋值 */
vec[0] = std::move(vec[1]);		// 需要使用move修饰,使得程序员知道后果cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;// 3 会自动调用delete [] 函数去释放内存
unique_ptr<int[]> array(new int[5]);	// 支持这样定义

// 1 构造,并自定义析构
class Test {
public:Test() { cout << "Test的构造函数..." << endl; }~Test() { cout << "Test的析构函数..." << endl; }void doSomething() { cout << "do something......" << endl; }
};// 自定义一个内存释放其
class DestructTest {public:void operator()(Test *pt) {pt->doSomething();delete pt;}
};// 2 主动释放对象
unique_ptr<Test> t9(new Test);
t9 = NULL;
t9 = nullptr;
t9.reset();// 3 放弃对象所有权
Test *t10 = t9.release();

问题

auto_ptr<string> p1;
string *str = new string("智能指针的内存管理陷阱");
p1.reset(str);	// p1托管str指针
{auto_ptr<string> p2;p2.reset(str);	// p2接管str指针时,会先取消p1的托管,然后再对str的托管
}// 此时p1已经没有托管内容指针了,为NULL,在使用它就会内存报错!
cout << "str:" << *p1 << endl;

shared_ptr

熟悉了unique_ptr 后,其实我们发现unique_ptr 这种排他型的内存管理并不能适应所有情况,有很大的局限!如果需要多个指针变量共享怎么办?

如果有一种方式,可以记录引用特定内存对象的智能指针数量,当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放它!这就是 shared_ptr 采用的策略!
 

// 1 构造
shared_ptr<Person> sp1;
Person *person1 = new Person(1);
sp1.reset(person1);	// 托管person1shared_ptr<Person> sp2(new Person(2));
shared_ptr<Person> sp3(sp1);

不同初始化的性能差异

struct A;
std::shared_ptr<A> p1 = std::make_shared<A>();
std::shared_ptr<A> p2(new A);

上面两者有什么区别呢? 区别是:std::shared_ptr构造函数会执行两次内存申请,而std::make_shared则执行一次。

std::shared_ptr在实现的时候使用的refcount技术,因此内部会有一个计数器(控制块,用来管理数据)和一个指针,指向数据。因此在执行std::shared_ptr<A> p2(new A)的时候,首先会申请数据的内存,然后申请内控制块,因此是两次内存申请,而std::make_shared<A>()则是只执行一次内存申请,将数据和控制块的申请放到一起。那这一次和两次的区别会带来什么不同的效果呢?

void f(std::shared_ptr<Lhs> &lhs, std::shared_ptr<Rhs> &rhs){...}f(std::shared_ptr<Lhs>(new Lhs()),std::shared_ptr<Rhs>(new Rhs())
);

因为C++允许参数在计算的时候打乱顺序,因此一个可能的顺序如下:

  1. new Lhs()
  2. new Rhs()
  3. std::shared_ptr
  4. std::shared_ptr

此时假设第2步出现异常,则在第一步申请的内存将没处释放了,上面产生内存泄露的本质是当申请数据指针后,没有马上传给std::shared_ptr。

weak_ptr

weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。 同时weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。

弱指针的使用;
weak_ptr wpGirl_1; // 定义空的弱指针
weak_ptr wpGirl_2(spGirl); // 使用共享指针构造
wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针

弱指针也可以获得引用计数;
wpGirl_1.use_count()

弱指针不支持 * 和 -> 对指针的访问;
 

shared_ptr<Girl> sp_girl;
sp_girl = wpGirl_1.lock();// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;

3.how?

shared_ptr

// 定义一个名为 Type 的类
class Type
{
public:int a = 1; // 定义一个整数成员变量a,并初始化为1
};// 定义一个名为 share_count 的类,用于跟踪共享计数
class share_count {
public:share_count() : _count(1) {} // 构造函数,初始化共享计数为1void add_count() {++_count; // 增加共享计数}long reduce_count() {return --_count; // 减少共享计数并返回新值}long get_count() const {return _count; // 获取当前共享计数的值}
private:long _count; // 保存共享计数的私有成员变量
};// 定义一个模板类 smart_ptr,用于管理动态分配的对象
template<typename T>
class smart_ptr
{
public:smart_ptr(T* ptr = NULL) : m_ptr(ptr) {if (ptr) {m_share_count = new share_count; // 如果指针非空,创建一个共享计数对象}}~smart_ptr() {if (m_ptr && !m_share_count->reduce_count()) {delete m_ptr; // 如果计数归零,删除对象delete m_share_count; // 删除共享计数对象cout << "~smart_ptr" << endl; // 输出析构消息}}T& operator*() const { return *m_ptr; } // 解引用操作符,函数被定义为只读的T* operator->() const { return m_ptr; } // 成员访问操作符operator bool() const { return m_ptr; } // 类型转换为布尔值,用于检查指针是否有效// 性能优化: 编译器在处理 noexcept 函数时可以进行一些性能优化,因为它们知道这些函数不会引发异常。// 这可以在某些情况下提高程序的性能。// 拷贝构造函数,实现共享计数的复制smart_ptr(const smart_ptr& rhs) noexcept {m_ptr = rhs.m_ptr;m_share_count = rhs.m_share_count;m_share_count->add_count(); // 增加共享计数}// 拷贝赋值运算符,实现共享计数的复制// 在拷贝赋值运算符中,通常返回引用的原因是为了支持链式赋值。smart_ptr& operator=(const smart_ptr& rhs) noexcept {m_ptr = rhs.m_ptr;m_share_count = rhs.m_share_count;m_share_count->add_count(); // 增加共享计数// 返回当前对象的引用return *this;}// 获取当前共享计数的值long use_count() const {if (m_ptr) {return m_share_count->get_count();}return 0;}
private:T* m_ptr; // 指向动态分配对象的指针share_count* m_share_count; // 指向共享计数对象的指针
};int main()
{smart_ptr<Type> sptr(new Type); // 创建智能指针,管理一个 Type 对象cout << "sptr的共享计数:" << sptr.use_count() << endl;smart_ptr<Type> sptr2(sptr); // 使用拷贝构造函数创建另一个智能指针,共享同一个对象cout << "sptr2的共享计数:" << sptr2.use_count() << endl;smart_ptr<Type> sptr3; // 创建另一个智能指针sptr3 = sptr2; // 使用拷贝赋值运算符,共享同一个对象cout << "sptr3的共享计数:" << sptr3.use_count() << endl;return 0;
}// 输出示例:
// sptr的共享计数:1
// sptr2的共享计数:2
// sptr3的共享计数:3
// ~smart_ptr:析构消息,当 sptr、sptr2 和 sptr3 超出作用域时触发析构函数

参考

C++ 智能指针 - 全部用法详解-CSDN博客

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

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

相关文章

“超人练习法”系列07:熟练掌握的几个阶段

在上一篇文章中&#xff0c;我们谈到了技能内化阶段&#xff0c;以开车为例&#xff0c;继续展示一个人在具体任务中的需求和挑战&#xff0c;是如何通过不断迭代而发生变化的。 第一个阶段&#xff0c;无意识的无能 有人说&#xff0c;无知是福&#xff0c;傻人有傻福。当你…

【AIGC】Controlnet:基于扩散模型的文生图的可控性

前言 controlnet可以让stable diffusion的生图变得可控。 文章连接&#xff1a;https://arxiv.org/pdf/2302.05543.pdf 摘要 冻结了stable diffusion的预训练模型并重用它的预训练编码层神经网络结构与零初始化卷积层连接&#xff0c;从零开始逐渐增加参数&#xff0c;并确…

Matlab:toposort

语法&#xff1a; n toposort(G) %调用toposort函数&#xff0c;对有向图G进行拓扑排序&#xff0c;并将排序结果存储在变量n中 n toposort(G,Order,algorithm) [n,H] toposort(___) %使用了两个输出参数的形式来调用toposort函数。除了返回排序结果n外&am…

数字集成电路VLSI复习笔记

逻辑门符号 Inverter CMOS NAND Gate CMOS NOR Gate MOS Capacitor nmos cutoff Linear Saturation Channel Charge Carrier velocity nMOS Linear I-V nMOS Saturation I-V Summary nMOS Operation pMOS Operation Inverter Step Response Delay Definitions 3-input NAND Ca…

只不过孤岛罢了:我的2023年总结

2023已悄然过去&#xff0c;还记得跨年夜那天&#xff0c;我突然接到一星期要期末考的消息&#xff0c;我的内心是多么奔溃&#xff0c;先不说一天一门强度如此之高&#xff0c;重要的是矩阵论&#xff0c;工程优化等等科目&#xff0c;还要速成&#xff0c;于是麻木得预习一日…

怎么理解接口幂等,项目中如何保证的接口幂等

都 2024 年了&#xff0c;竟然还有人不知道接口幂等是什么东西。 hi&#xff0c;大家好&#xff0c;我是 浮生 今天正好有空&#xff0c;给大家分享一下 幂等的实现。 什么是幂等&#xff1f; 一、问题解析 简单来说&#xff0c;就是一个接口&#xff0c;使用相同的参数重复执…

2.右值引用和移动语义

文章目录 右值引用和移动语义&&的特性右值引用优化性能&#xff0c;避免深拷贝移动(move )语义forward 完美转发emplace_back 减少内存拷贝和移动unordered container 无序容器map和unordered_map的差别内部实现机理不同优缺点以及适用处 小结优缺点以及适用处 小结 代…

JavaScript音视频,使用JavaScript如何在浏览器录制电脑摄像头画面为MP4视频文件并下载视频文件到本地

前言 本章介绍使用JavaScript如何在浏览器录制电脑摄像头画面为MP4视频文件并下载视频文件到本地。 实现功能 1、使用navigator.mediaDevices.getUserMedia获取摄像头画面 2、将获取到的摄像头画面渲染到canvas画板上 3、将canvas转换为blob对象 4、通过document.createElem…

哈希表的实现(1)----除留余数法实现

一&#xff0c;哈希表的介绍 哈希表是一种通过哈希思想实现的一种数据结构。哈希表这种数据结构的特点便是可以通过一个值快速的定位这个值所在的位置实现插入&#xff0c;删除&#xff0c;查找。在这篇博客里面&#xff0c;我们便来实现一个通过除留余数法实现的一个哈希表。 …

【软件工程】项目管理与迭代开发:DevOps平台、敏捷协作平台与软件需求交付

文章目录 1、项目管理与软件需求交付2、DevOps平台3、敏捷协作平台 1、项目管理与软件需求交付 软件需求交付方法&#xff1a; DevOps&#xff1a;DevOps是一种软件开发和运维的方法论&#xff0c;它强调开发团队和运维团队之间的紧密协作和沟通&#xff0c;以实现快速、高效、…

laravel 中间件记录日志

前提 我希望通过中间件记录用户的请求数据、我的返回数据&#xff0c;如果出现异常捕获异常。 代码 路由文件&#xff1a;追加中间件api-logging&#xff0c;用于记录日志 Route::prefix(api)->middleware([api, api-logging])->group(function () {...路由内容 });n…

IntersectionObserver

IntersectionObserver 这个API主要实现图片懒加载、加载更多等等。 该API作用是观察两个元素之间有没有交叉&#xff0c;有没有重叠 现在要做的是当图片跟视口有交叉的情况下&#xff0c;把data-src的图片路径替换给src属性 //第一个参数是 回调&#xff0c;第二个参数的 配置…

HarmonyOS4.0 系列——06、渲染之条件渲染、循环渲染以及懒加载渲染

HarmonyOS4.0 系列——06、渲染之条件渲染、循环渲染以及懒加载渲染 if/else&#xff1a;条件渲染 ArkTS 提供了渲染控制的能力。条件渲染可根据应用的不同状态&#xff0c;使用 if、else 和 else if 渲染对应状态下的 UI 内容。 写法和 TS 的一样&#xff0c;简单看一下即可…

【数据结构之树和二叉树】

数据结构学习笔记---007 数据结构之树和二叉树概念篇1、树的概念和结构1.1、树的相关概念1.2、树的存储结构 2、二叉树概念及结构2.1、二叉树概念2.2、满二叉树2.3、完全二叉树2.4、满二叉树或完全二叉树的存储形式 3、堆的概念及结构3.1、堆的性质3.2、堆的意义 4、二叉树的存…

python_selenium_安装基础学习

目录 1.为什么使用selenium 2.安装selenium 2.1Chrome浏览器 2.2驱动 2.3下载selenium 2.4测试连接 3.selenium元素定位 3.1根据id来找到对象 3.2根据标签属性的属性值来获取对象 3.3根据xpath语句来获取对象 3.4根据标签的名字获取对象 3.5使用bs4的语法来获取对象…

解惑:测试圈网红工具 Jmeter 到底难在哪里

作为一名测试人员&#xff0c;你是否也曾经遇到过这些问题&#xff1a; 同样的起点&#xff0c;同样的工作时间&#xff0c;为什么别人接那么多项目&#xff0c;你还是在点点点&#xff1b;为什么别人升职了&#xff0c;而你还在原地踏步&#xff1f; 同样的工作内容&#xf…

GNU Tools使用笔记

GCC命令选项 预处理器选项 -M选项 作用&#xff1a;生成源文件的依赖关系&#xff0c;包含了该源文件所有的依赖&#xff0c;默认会发送到标准输出&#xff08;stdout&#xff09;。 示例1&#xff1a;在ubuntu中&#xff0c;新建如下main.c、main.h、head1.h、head2.h文件。…

数据库的数据类型

文章目录 前言一、数据类型数据类型分类数值类型bit类型小数类型floatdecimal 字符串类型charvarcharchar和varchar比较 日期和时间类型enum和set 前言 一、数据类型 数据类型分类 数值类型 下面我们来创建一个表&#xff0c;表中创建一个tinyint类型的数据。当我们不指定tiny…

【贪心】重构字符串

/*** 思路&#xff1a;如果s长度小于2&#xff0c;直接返回s&#xff0c;假设字符串s的长度为n。* n为偶数&#xff0c;如果字符串中的某个字符数量超过 n/2 则肯定会存在相邻的字符。* n为奇数&#xff0c;如果字符串中的某个字符的数量超过 &#xff08;n1&am…

机器学习顶会ICML 2024今日开放投稿,CCF A类,中稿率27.94%(附ICML23杰出论文+18篇高分论文)

ICML 2024今天开放投稿了&#xff01;距离截稿还有24天&#xff0c;想冲ICML的同学速度&#xff01; ICML 全称 International Conference on Machine Learning&#xff0c;由国际机器学习学会&#xff08;IMLS&#xff09;举办&#xff0c;与NIPS一同被认为是人工智能、机器学…