我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++ smart pointer全面深入讲解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++ smart pointer全面深入讲解

我们为什么需要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内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++ smart pointer全面深入讲解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C++标准模板库STL深入讲解

STL提供了一组表示容器、迭代器、函数对象和算法的模板。容器是一个与数组类似的单元,可以存储若干个值。STL容器是同质的,即存储的值的类型相同:算法是完成特定任务(如对数组进行排序或在链表中查找特定值)的处方
2022-12-26

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录