智能指针(unique_ptr,shared_ptr,weak_ptr)

01 引入

在实际的C++开发中,我们经常会遇到诸如程序运行中突然崩溃,程序运行所用内存越来越多最终不得不重启等问题,这些问题往往都是内存资源管理不当造成的(内存泄漏)
比如:

        有些内存资源已经被释放,但指向它的指针并没有改变指向(成为了野指针),并且后续还在使用

        int *p = new int[100];

        delete [] p; // p指向的空间已经被释放

        

        // p指向了已经释放的空间

        *p = 1024; // 操作不属于自己的内存

        

        有些内存资源已经被释放,后期又试图再释放一次(重复释放同一块内存会导致程序运行奔溃) ----- double free

        

        没有及时释放不再使用的内存资源,造成内存泄漏,程序占用的内存资源越来越多

                void get_memory(char *p) {

                        p = new char[100];

                }

        针对以上这些情况,很多程序员认为C++语言应该提供更友好的内存管理机制,这样就可以将精力集中于开发项目的各个功能上


事实上,内存管理的替代方案很早就有了,早在1959年前后,就有人提出了“垃圾自动回收”机制。所谓垃圾,指的是那些不再使用或者没有任何指针指向的内存空间,而“回收”则指的是将这些“垃圾”收集起来以便再次利用

如今,垃圾回收机制已经大行其道,得到了诸多编程语言的支持,例如:Java,Python,C#,PHP等
而C++虽然从来没有公开的支持过垃圾回收机制,但C++98/03标准中,支持使用auto_ptr智能指针来实现堆内存的自动回收

C++11新标准在废弃auto_ptr的同时,增添了unique_ptr,shared_ptr以及weak_ptr这3个智能指针来实现堆内存的自动回收
 

所谓智能指针,可以从字面上理解为“智能”的指针,具体来讲,智能指针和普通指针的用法是相似的(->/*),不同之处在于,智能指针可以在适当时机自动释放分配的内存,也就是说,使用智能指针可以很好地避免“忘记释放内存而导致内存泄漏”问题出现,由此可见,C++也逐渐开始支持垃圾回收机制了,尽管目前支持程度还有限


C++智能指针底层是采用引用计数的方式实现的,简单的理解,智能指针在申请堆内存空间的同时,会为其配备一个整形值(初始值为1),每当有新对象使用此堆内存时,该整形值+1,反之,每当使用此堆内存的对象被释放时,该整形值减1,当堆空间对应的整形值为0时,即表明不再有对象使用它,该堆空间就会被释放掉

02 C++11 shared_ptr智能指针

实际上,每种智能指针都是以类模板的方式实现的,shared_ptr 也不例外

shared_ptr<T> (其中T表示指针指向的具体数据类型)的定义位于<memory>头文件,并位于std命名空间中,因此在使用该类型指针时,程序中应包含如下两行代码:

        #include <memory>
        using namespace std;

注意,第二行代码并不是必须的,也可以不添加,则后续在使用shared_ptr智能指针时,就需要明确指明std::

​​​​​​​

值的一提的是,和unique_ptr,weak_ptr不同之处在于,多个shared_ptr智能指针可以共同使用同一块堆内存,并且,由于该类型智能指针在实现上采取的是引用计数机制,即便有

一个shared_ptr指针放弃了堆内存的“使用权”(引用计数减1),也不会影响其他指向同一堆内存的shared_ptr指针(只有引用计数为0时,堆内存才会被自动释放)

1. shared_ptr智能指针的创建

shared_ptr<T>类模板中,提供了多种实用的构造函数,这里列举了几个常用的构造函数

(以构建指向int类型数据的智能指针为例)

        (1) 通过如下两种方式,可以构造出shared_ptr<T>类型的空智能指针

                        std::shared_ptr<int> p1; // 不传入任何实参

                        std::shared_ptr<int> p2(nullptr); // 传入空指针  nullptr

                注意:空的shared_ptr 指针,其初始引用计数为0,而不是1
                           p1和p2不管理任何堆空间

        (2) 在构建shared_ptr智能指针,也可以明确其指向

                例如:

                        std::shared_ptr<int> p3(new int(10));

                        由此,我们就成功构建了一个shared_ptr智能指针,其指向一块存有10这个int类型数据的堆内存空间

                        同时,C++11标准中还提供了std::make_shared<T>模板函数,其可以用于初始化shared_ptr智能指针,例如:

                                std::shared_ptr<int> p3 = std::make_shared<int>(10);

                        以上两种方式创建的p3是完全相同的


        (3) 除此之外,shared_ptr<T>模板还提供有相应的拷贝构造函数和移动构造函数

                例如:

                        // 调用拷贝构造函数

                                std::shared_ptr<int> p4{p3};

                                // 或者 std::shared_ptr<int> p4 = p3;

                        // 调用移动构造函数

                                std::shared_ptr<int> p5(std::move(p4));

                                // 或者 std::shared_ptr<int> p5 = std::move(p4);

                如上所示,p3和p4都是shared_ptr类型的智能指针,因此可以用p3来初始化p4,由于p3是左值,因此会调用拷贝构造函数,需要注意的是,如果p3为空智能指针,则p4也为空智能指针,其引用计数初始值为0,反之,则表明p4和p3指向同一块堆内存,同时该堆空间的引用计数会加1,而对于std::move(p4)来说,该函数会强制将p4转换成对应的右值,因此初始化p5调用的是移动构造函数,另外和调用拷贝构造函数不同,用std::move(p4)初始化p5,会使得p5拥有了p4的堆内存,而p4则变成了空智能指针

                注意,同一普通指针不能同时为多个shared_ptr对象赋值,否则会导致程序发生异常,例如:

                        int *ptr = new int; // 开辟了一块空间

                        std::shared_ptr<int> p1(ptr);

                        std::shared_ptr<int> p2(ptr); // ERROR

                p1和p2的引用计数是单独算的,它们任意一个对象在析构的时候,都会销毁ptr所指的对象,ptr就为悬空指针,所以,这个对象会被“销毁两次”


        (4) 在初始化shared_ptr智能指针时,还可以自定义所指堆内存的释放规则,这样当堆内存的引用计数为0时,会优先调用我们自定义的释放规则

                在某些场景中,自定义释放规则是很有必要的,比如,对于申请的动态数组来说,shared_ptr 指针默认的释放规则是不支持释放数组的,只能自定义对应的释放规则,才能正确地释放申请的堆内存

                对于申请的动态数组,释放规则可以使用C++11标准中提供的default_delete<T>模板类,我们也可以自定义释放规则:


实际上借助lambda表达式,我们还可以像如下这样初始化p7,它们是完全相同的

        std::shared_ptr<int> p7(new int[10], [](int *p){ delete [] p; });

shared_ptr<T>模板类还提供有其它一些初始化智能指针的方法


​​​​​​​​​​​​​​

                     
            

                


 

                        

                        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值