返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++ smart pointer全面深入讲解
  • 187
分享到

C++ smart pointer全面深入讲解

2024-04-02 19:04:59 187人浏览 八月长安
摘要

目录我们为什么需要smart pointersmart pointer基本概念之引用计数smart pointer之shared_ptr自定义deleter(也就是自定义删

我们为什么需要smart pointer

众所周知 新手写的c++代码是很恐怖 压根就不能用 其中最大的原因就在于新手写的代码可能存在大量的内存泄漏 那么为什么新手无法很好的去掌握内存的东西呢 就是因为原生的c++并不像java那样存在垃圾回收的机制 申请在堆区的资源都需要自己去回收 然而最痛苦的一件事情在于 指针的生命周期结束时 你会不小心就没去回收他在堆区的资源 因为堆区资源的生命周期是很难把握的 有可能你析构了 直接导致野指针访问异常那么为了解决这个问题 c++就推出了智能指针 其中最重要的三种指针就是shared_ptr unique_ptr weak_ptr 接下来让我们来讲讲如何将智能指针的生命周期和堆区资源的生命周期绑定起来吧

其实也非常简单 本质就是当这片堆区资源的引用计数变为0的时候就释放这片内存

smart pointer基本概念之引用计数

先来说说引用计数 这个东西是stl保证了肯定是线程安全的 所以即使你在多个线程内同时去增加或者同时减少引用计数也并不会让引用计数的值出现非你预期的结果

智能指针是和引用计数绑定在一起的 当你创建智能指针指向一片资源时 引用计数就加一 当智能指针析构时 引用计数就减一 当引用计数变为0时 堆区资源被析构

smart pointer之shared_ptr

让我们来看看下一段代码

int main()
 {
	std::shared_ptr<std::string> i(new std::string("its Good"));
	std::shared_ptr<std::string> j(new std::string("its bad"));
	std::vector<std::shared_ptr<std::string>> smartPointer_vec;
	for(int k=0;k<5;k++)
	smartPointer_vec.emplace_back(i);
	for (int k = 0; k < 4; k++)
	smartPointer_vec.emplace_back(j);
	for (auto &i : smartPointer_vec)
	{
		std::cout<<i->c_str();
		std::cout << i.use_count() << " ";
		std::cout << j.use_count() << std::endl;
		i = nullptr;
	}
	std::cout << i->c_str();
	std::cout << i.use_count() <<" ";
	std::cout << j.use_count() << std::endl;
}

聪明人看输出 你就能完全明白 当引用计数为0的时候就会析构 其他不多说了

重要讲解:首先使用share_ptr去指向new出来的数据是性能低效的 最本质的原因在于 他会进行两次内存分配 第一次是对象堆区资源的申请 然后才是引用计数堆区资源的申请 而使用make_shared可以只进行一次内存分配 所以他更快 并且更安全 并且c++标准委员会也推荐你这么做 关于make_shared等下讲解

自定义deleter(也就是自定义删除器)

先说我们为什么需要自定义删除器 因为在某些情况下 我们希望当智能指针指向的堆区资源释放的时候进行一些自定义操作也就是说你可以玩一些很花的操作 但是也是那句话 stl并不会执行任何安全检查 崩了需要自己负责并且总所周知 new []这种形式的堆区资源需要我们使用delete[]来释放 这就是最大的问题 shared_ptr默认是使用delete的 也就是说 当你使用shared_ptr去指向new []时如果不自定义删除器 必然会造成内存泄漏 如下图所示的一段代码就是经典的内存泄漏

正确的写法如下

即自定义一个删除器 当然你也可以玩一些移动操作 也就是花哨的操作 当然花哨操作就很多了 我只演示其中一种如下图所示

运行结果截图如下:

Tips:当你非常清楚你在干什么的时候再玩 功力不够 不要乱玩

shared_ptr之make_shared

上文我们说过 使用智能指针指向new出来的资源有一个问题就是他会进行两次内存分配 而标准委员会推荐创建shared_ptr的方式是使用make_shared 让我们来看看make_shared是如何进行堆区资源申请的 一个最简单的例子如下

int main()
{
	std::shared_ptr<int>p1(new int(5));
	//下面这种方式比上面这种方式性能更快 并且更加安全
	std::shared_ptr<int>p2 = make_shared<int>(5);
}

当你使用make_shared的时候 又想去使用智能指针指向一个数组的时候 一个推荐的做法如下

int main()
{
	std::shared_ptr<std::vector<int>>p1(new std::vector<int>());
	//下面这种方式比上面这种方式性能更快 并且更加安全
	std::shared_ptr<std::vector<int>>p2 = make_shared<std::vector<int>>();
}

智能指针存在的问题之循环引用

那么现在我们来看看shared_ptr存在的一些问题 其中比较著名的一个问题就是循环引用 什么叫循环引用呢 本人的观点是当你的智能指针指向的A堆区资源里又有智能指针去指向B堆区资源 而B堆区资源又存在一个智能指针来指向A堆区资源 而你能拿到的指针对半是全局或者是栈区的智能指针 你无法干预到堆区的智能指针的释放 下面来看一个最简单的例子造成的循环引用 代码如下图所示

class SmartPointerTest
{
public:
	std::shared_ptr<SmartPointerTest> LoopRef{};
	int p[1000]{};
};
int main()
{
	std::shared_ptr<SmartPointerTest>p1(new SmartPointerTest());
	std::shared_ptr<SmartPointerTest>p2(new SmartPointerTest());
	p1->LoopRef = p2;
	p2->LoopRef = p1;
}

可以明显看到 我们创建了两个智能指针p1和p2 而p1指向的堆区资源里又有智能指针指向p2的堆区资源 同理p2 而当main函数结束的时候 p1 p2指针被释放 但是 这个时候 因为两片堆区资源的引用计数都没被置为0 所以不会释放 那么这片堆区内存也就永远的泄漏了 这是所有循环引用的原型 无论任何再复杂的循环引用都是建立在这个最基本的循环引用之上的

解决循环引用之weak_ptr

我们现在希望有一个方法来解决循环引用的问题 并且我们也想去随时拿到资源 那么我们该如何做呢 标准委员会也考虑到了这个问题 于是他提供了weak_ptr 当他指向一片堆区资源的时候 并不会让这片堆区资源的引用计数加一 而是作为这片资源的观察者 当需要这片资源的时候 随时使用lock()函数来获得一个shared_ptr来进行使用 下面让我们来看看如何使用weak_ptr 基于上面的例子

class SmartPointerTest
{
public:
	std::weak_ptr<SmartPointerTest> LoopRef{};
	int p[1000]{};
};
int main()
{
	std::shared_ptr<SmartPointerTest>p1(new SmartPointerTest());
	std::shared_ptr<SmartPointerTest>p2(new SmartPointerTest());
	p1->LoopRef = p2;
	p2->LoopRef = p1;
	//当你想使用资源的时候 用下面的操作进行
	std::cout << p1->LoopRef.lock()->p << std::endl;
}

输出结果如下:

Tips:当然weak_ptr的作用远远不止如此 他存在的意义仅仅是你想共享资源但是你并不想增加引用计数 解决循环引用只是顺便解决的优秀的程序员总是能知道在什么情况下使用何种指针来达到性能最优 lock()函数 顾名思义是要去给引用计数上的 频繁上锁带来的性能问题不用多说了吧

如果weak_ptr指向的资源已经被析构 那么他会抛出bad_weak_ptr的异常 请注意捕获异常

智能指针问题

无法创建指向自己的智能指针(本质当创建自己的智能指针时会创建两个所属组)

什么叫无法创建指向自己的智能指针呢 看如下这段代码

class SmartPointerTest
{
public:
	std::weak_ptr<SmartPointerTest> LoopRef{};
	int p[1000]{};
	std::vector<std::shared_ptr<SmartPointerTest>> spt_vec;
	void MemberFuncTest()
	{
		spt_vec.push_back(std::shared_ptr<SmartPointerTest>(this));
	}
	int operator[](int i)
	{
		return p[i];
	}
};
int main()
{
	std::shared_ptr<SmartPointerTest>p1(new SmartPointerTest());
	p1->MemberFuncTest();
	std::cout<<p1.use_count()<<std::endl;
	system("pause");
}

我们预期的结果是把指向自己的智能指针传入 并且引用计数为2 但是运行结果如下:

并且程序会崩溃 为什么呢 因为你重复释放了 这就是我说的 你会创建两个组 而不是单纯的增加引用计数 其本质还是滥用普通指针和智能指针引起的麻烦

解决方法如下

代码如下 我们可以继承于std::enable_shared_from_this来解决

class SmartPointerTest :std::enable_shared_from_this<SmartPointerTest>
{
public:
	std::weak_ptr<SmartPointerTest> LoopRef{};
	int p[1000]{};
	std::vector<std::shared_ptr<SmartPointerTest>> spt_vec;
	void MemberFuncTest()
	{
		spt_vec.push_back(std::shared_ptr<SmartPointerTest>(shared_from_this()));
	}
	int operator[](int i)
	{
		return p[i];
	}
};
int main()
{
	std::shared_ptr<SmartPointerTest>p1(new SmartPointerTest());
	p1->MemberFuncTest();
	std::cout<<p1.use_count()<<std::endl;
	system("pause");
}

当你这样继承自enable_shared_from_this的时候你就可以将自身的智能指针传入而不是创建一个新的组避免了重复释放非常的方便

关于unique_ptr我们将会在下一篇文章进行详细讲解其实也很简单就是他堆区资源的引用计数永远只可能是一也就是说他的资源只可能被一个指针指向附带而来的有一些小细节和普通的shared_ptr不同我们也就留在下一章再说了

到此这篇关于C++ smart pointer全面深入讲解的文章就介绍到这了,更多相关C++ smart pointer内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++ smart pointer全面深入讲解

本文链接: https://lsjlt.com/news/165938.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++ smart pointer全面深入讲解
    目录我们为什么需要smart pointersmart pointer基本概念之引用计数smart pointer之shared_ptr自定义deleter(也就是自定义删...
    99+
    2024-04-02
  • Android——Theme和Style-由浅入深,全面讲解
    1、官方详细解读 样式和主题背景  |  Android 开发者  |  Android Developers 2、应用场景 类似web设计中css样式。将应用设计的细节与界面的结构和行为分开。 样式style :应用于 单个 View ...
    99+
    2023-09-01
    Android Theme主题背景 Style样式
  • Java由浅入深全面讲解方法的使用
    目录一、方法的概念及其使用1.1、什么是方法1.2、方法的定义1.3、方法调用的执行过程1.4、实参和形参的关系(重要)二、方法的重载2.1、为什么需要方法重载2.2、方法重载的定义...
    99+
    2024-04-02
  • C/C++深入讲解内存管理
    目录C/C++内存分布C语言中的动态内存管理C++的内存管理operator new与operator delete函数operator new与operator dele...
    99+
    2024-04-02
  • C++深入讲解哈夫曼树
    目录哈夫曼树的基本概念1)路径2)路径长度3)权4)结点的带权路径长度5)树的带权路径长度6)哈夫曼树哈夫曼树的构造算法哈夫曼树的构造过程哈夫曼树算法的实现1)结点的存储结构2)构建...
    99+
    2024-04-02
  • C++深入分析讲解链表
    目录链表的概述1、数组特点2、链表的概述3、链表的特点静态链表链表的操作1、链表插入节点头部之前插入节点尾部之后插入节点有序插入节点2、遍历链表节点3、查询指定节点4、删除指定节点5...
    99+
    2024-04-02
  • C++深入讲解函数重载
    目录函数重载概念重载依据值型别判断函数重载的规则名字粉碎-名字修饰函数重载 概念 在C++中可以为两个或者两个以上函数提供相同的函数名称,只要参数类型不同,或者参数数目不同,参数顺序...
    99+
    2024-04-02
  • C++超全面讲解多态
    目录多态的概念多态的定义及实现构成条件虚函数虚函数的重写虚函数重写的两个例外抽象类抽象类的概念接口继承和实现继承多态的原理虚函数表多态的原理多态的概念 概念:通俗的来说就是多种形态,...
    99+
    2024-04-02
  • C++多态的全面讲解
    目录1.多态的定义和实现多态的浅层理解多态的构成条件2.虚函数虚函数的重写规则虚函数重写条件的两个例外1.协变(返回值不同)2.析构函数的重写(函数名不同)3.C++11 overr...
    99+
    2024-04-02
  • YII2 全局异常处理深入讲解
    首先,我们必须理性认识到,任何一个即使稳定的系统中也是存在着大量的 bug,不管是因为什么原因导致的错误,我们都是需要做好防范的,最好的结果当然是将异常纠正过来,返回客户端一个正确...
    99+
    2024-04-02
  • C++深入分析讲解智能指针
    目录1.简介2.unique_ptr指针(独占指针)3.shared_ptr指针(共享所有权)4.weak_ptr(辅助作用)5.自实现初级版智能指针6.总结1.简介 程序运行时存在...
    99+
    2024-04-02
  • C++深入浅出讲解函数重载
    目录前言函数重载1.1 函数重载的概念1.2 函数重载的意义1.3 名字修饰(name Mangling)1.4 extern "C"前言 自然语言中,一个词可以...
    99+
    2024-04-02
  • C++标准模板库STL深入讲解
    目录认识STLSTL的概述STL标准模板库都有什么容器算法迭代器函数符空间配置器string字符容器库vector容器vector容器于array数组容器的区别空间分配策略迭代器非法...
    99+
    2022-12-26
    C++标准模板库STL C++标准模板库 C++ STL
  • C++深入浅出讲解缺省参数
    目录缺省参数定义用法缺省参数 一般情况下,函数调用时的实参个数应与形参相同,但为了更方便地使用函数,C++也允许定义具有缺省参数的函数,这种函数调用时,实参个数可以与形参不相同。 定...
    99+
    2024-04-02
  • C++泛型模板约束深入讲解
    CPP参考:(新标准) 传送门 模板对于类型的约束: 约束 template_get_size 泛型T只允许接受类型:list<T>,其实为 C/C++ 泛型模板例化特性...
    99+
    2024-04-02
  • C++全面细致讲解复数类
    目录一、复数类应该具有的操作二、利用操作符重载三、注意事项四、小结一、复数类应该具有的操作 运算:+,- ,*,/比较:== ,! =赋值:=求模:modulus 二、利用操作符重载...
    99+
    2024-04-02
  • c++深入浅出讲解堆排序和堆
    目录堆是什么最大堆最小堆堆排序最终代码关于堆堆是什么 堆是一种特殊的完全二叉树 如果你是初学者,你的表情一定是这样的 别想复杂 首先,你一定见过这种图 咱们暂时不管数字 这就是一个...
    99+
    2024-04-02
  • C语言深入讲解函数的使用
    目录关于函数1. 函数的定义形式2. 函数的声明3. 返回语句4. 函数参数4.1 形式参数(传值调用)4.2 实际参数(传址调用)4.3 无参数5. 函数的调用5.1 嵌套调用5....
    99+
    2024-04-02
  • C语言深入讲解链表的使用
    目录一、链表的概念二、链表的分类1. 单向或者双向链表2. 带头或者不带头(是否有自带哨兵位头结点)3. 循环或者非循环链表4. 无头单向非循环链表和带头双向循环链表3、链表的实现(...
    99+
    2024-04-02
  • C++深入分析讲解类的知识点
    目录知识点引入类的初识1、封装2、权限3、类的定义(定义类型)4、类的成员函数与类中声明及类外定义Person类的设计设计立方体类点Point和圆Circle的关系知识点引入 C语言...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作