一、引入
首先通过一个使用场景来引入智能指针。这里有一个类 HF,一个子函数 fun,在 fun 里面 new 了三个 HF 对象,然后 delete。正常情况下 delete 会先调用析构函数,然后再释放相应的资源。
C++ 智能指针通过 RAII 机制和指针行为重载实现内存自动管理。主要解决手动 new/delete 导致的资源泄漏问题。介绍了 auto_ptr、unique_ptr、shared_ptr 和 weak_ptr 的区别。shared_ptr 使用引用计数,存在循环引用风险,需配合 weak_ptr 解决。文章还阐述了内存泄漏的定义、危害及分类(堆内存泄漏、系统资源泄漏)。

首先通过一个使用场景来引入智能指针。这里有一个类 HF,一个子函数 fun,在 fun 里面 new 了三个 HF 对象,然后 delete。正常情况下 delete 会先调用析构函数,然后再释放相应的资源。
是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。(通俗来讲,就是将资源交给一个类对象来管理,通过该类的构造函数交给对象。)
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。
(1)、不需要显式地释放资源,而是通过智能指针间接帮忙释放;(2)、采用这种方式,对象所需的资源在其生命期内始终保持有效。
智能指针实际也是一个类,要是行为像一个指针,即要重载解引用(*),箭头(->),甚至有时还要重载方括号([])。
首先我们实现一个简易版智能指针:new 了一个日期类对象交给智能指针管理,智能指针对象存在期间,资源都是存在的,最后智能指针对象生命周期结束,调用析构函数释放,同时释放掉资源(delete);
但是当我们用对象 sp1 去拷贝构造 sp2 时,此时就会报错。原因在于我们没有实现拷贝构造,此时默认拷贝构造就是浅拷贝,这样两个对象的成员变量都会指向这份资源,最后调用析构函数时,就会对这份资源 delete 两次,从而造成野指针的问题。如何解决这个问题,在第四点进行介绍。
头文件:memory。具体信息可以查看官网文档。
auto_ptr 是直接将资源的管理权转移,用对象 sp1 去拷贝构造 sp2,那么就会将 sp1 的资源的管理权交给 sp2 管理,而 sp1 被置空。
大致处理方法如下:拷贝后将 sp1 置空就行了。
注意,C++11 的移动语义也是资源的转移,但和这里是不一样的,移动语义是针对将亡值去转移资源,而这里 sp1 不是将亡值。
这样做是有些问题的,这里资源转移后,sp1 就悬空了,此时拷贝后就不能去访问 sp1,否则就会出现空指针的问题,所以很多公司都禁止使用 auto_ptr。
提到智能指针,就得提一下 boost 库,boost 库是 C++ 第三方库,里面就有智能指针,而 C++ 的智能指针就是从这个库里面引入的,然后进行了略微修改。
该智能指针解决拷贝构造的问题的方法就是:简单粗暴,禁止拷贝,适用于不需要拷贝的场景。
底层实际就是将拷贝构造给 delete 了。同时,赋值运算符重载也要禁掉,默认生成的赋值运算符重载也是浅拷贝。
当遇到需要拷贝构造的场景时,就需要使用 shared_ptr,shared_ptr 解决拷贝构造的问题的方法是:引用计数,去解决多次析构的问题。
引用计数:记录当前有几个对象参与管理这个资源,在某个对象析构时,就将引用计数减 1,当最后一个对象析构时才去释放资源。
要实现引用计数,就需要一份资源对应一个计数,有人会想到定义一个静态成员 count,但实则不行,因为静态成员是属于整个类的,属于所有对象。管理一个资源的时候是可以解决的,但当第二个资源出现时,就不能适用了,因为不同资源之间的引用计数都是同一个静态成员变量,所以会相互影响。
实际上的实现如下:增加一个成员变量*pcount,即指向引用计数的指针,在构造的时候(即资源来了),就 new 一个计数给该指针,在拷贝构造发生的时候,除了使两个对象指向同一个资源外,两个对象的引用计数也要指向同一个,并且要记得把引用计数++一下,在某个对象析构时,就将引用计数减 1,然后判断是否为最后一个对象的析构,如果是的话就释放资源。
shared_ptr 虽然解决了拷贝构造的问题,但正因为引用计数的这样实现,又会造成赋值运算符重载后出现问题。
为了分析这里的缺陷,我们引入一个场景:双向链表的赋值。
这是双向链表的简单实现。因为会将链表资源交给智能指针管理,所以链表的定义中,成员 next 和 prev 的类型也应该是智能指针,不然在赋值的时候会出现类型不同的问题,正因为需要这样设计,问题就来了。
一般情况上述实现是没有问题的,但当执行下面两句代码后,问题就来了:这是在链接两个节点,链接完后就会这样。
首先出现两个节点分别由 n1 和 n2 指向,此时两个节点的引用计数分别都是 1,当执行 n1->next = n2 时,n2 指向的节点的引用计数就会变成 2;当执行 n2->prve = n1 时,n1 指向的节点的引用计数就会变成 2。
最后当析构链表时,这样就形成了一个闭环,导致这两个节点的内存泄漏,这个问题也叫循环引用。当两个 shared_ptr 互相引用就会出现循环引用的问题。
为了解决 shared_ptr 的循环引用问题,所以引入了 weak_ptr。
weak_ptr 的特点:没有引用计数,支持默认构造,构造函数的形参没有指针,因为该智能指针不参与资源管理,但自身成员变量会有一个指针,但会被置空,weak_ptr 的重点在于拷贝构造和赋值。
这里的不同是将链表的成员变量_next 和_prev 的类型变为 weak_ptr,正因为 weak_ptr 没有增加引用计数,所以在节点链接的时候,引用计数不会增加,所以节点会正常释放。
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
C++ 中我们一般关心两种分类:
(1)、堆内存泄漏 (Heap leak):堆内存指的是程序执行中依据须要分配通过 malloc / calloc / realloc / new 等从堆中分配的一块内存,用完后必须通过调用相应的 free 或者 delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生 Heap Leak。
(2)、系统资源泄漏:指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
在 C++ 里,手动管理动态分配的内存容易引发内存泄漏、悬空指针等问题。智能指针作为一种类模板,能有效管理动态分配的内存,避免这些问题的出现。C++ 标准库提供了三种主要的智能指针:std::unique_ptr、std::shared_ptr 和 std::weak_ptr。
std::unique_ptr
std::unique_ptr 属于独占式智能指针,它对所指向的对象拥有唯一的所有权。一旦 std::unique_ptr 被销毁,其指向的对象也会随之被自动销毁。
在 uniquePtrExample 函数中:借助 std::make_unique 创建了一个 std::unique_ptr,它指向 MyClass 的一个对象。调用 doSomething 方法来使用这个对象。尝试复制 std::unique_ptr 会引发编译错误,因为它不允许复制,不过可以使用 std::move 转移所有权。转移所有权之后,原 std::unique_ptr 变为空。
std::shared_ptr
std::shared_ptr 是共享式智能指针,多个 std::shared_ptr 能够指向同一个对象。它采用引用计数来管理对象的生命周期,当引用计数变为 0 时,对象就会被销毁。
在 sharedPtrExample 函数中:利用 std::make_shared 创建了一个 std::shared_ptr,它指向 MyClass 的一个对象。通过 use_count 方法可以查看当前的引用计数。复制 std::shared_ptr 会使引用计数增加。调用 reset 方法可以释放 std::shared_ptr,从而使引用计数减少。
std::weak_ptr
std::weak_ptr 是弱引用智能指针,它不拥有对象的所有权,只是对 std::shared_ptr 所管理的对象进行弱引用。std::weak_ptr 主要用于解决 std::shared_ptr 的循环引用问题。
在 weakPtrExample 函数中:定义了 A 和 B 两个类,其中 A 类包含一个 std::shared_ptr<B> 成员,B 类包含一个 std::weak_ptr<A> 成员。创建了 A 和 B 的 std::shared_ptr 对象,并相互引用。由于 B 类使用了 std::weak_ptr,所以不会出现循环引用,当 a 和 b 离开作用域时,对象能够被正确销毁。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online