• C++智能指针


    为了解决指针资源忘记或没有删除导致的内存泄露问题,C++就出现了智能指针的机制,可以在使用的时候初始化,在离开作用域之后就自动析构,删除资源

    C++98的智能指针

    auto_ptr的模拟实现

    auto_ptr是最早期的智能指针形态,
    它可以实现在

    • 构造函数里面初始化
    • 在析构函数里面将资源销毁,不用我们去显示调用,避免内存泄露
    • 但是它无法解决拷贝构造的问题,它是浅拷贝(会造成资源被多次析构)(使用管理权转移的方式来进行拷贝构造即构造之后,自己的资源就没了)
    namespace xzw
    {
        template <class T>
        class auto_ptr
        {
        private:
            T *_ptr;
    
        public:
            //在构造函数
            auto_ptr(T *ptr)
                : _ptr(ptr) //原生指针支持拷贝
            {
            }
            ~auto_ptr()
            {
                if (_ptr)
                {
                    cout << "delete" << endl;
                    delete _ptr; //析构函数把他清理
                }
            }
    
            //如何解决拷贝问题,管理权转移,只有一个人析构它
            auto_ptr(auto_ptr<T> &sp)
                : _ptr(sp._ptr)
            {
                //资源转给你,
                //把管理权转移
                sp._ptr = nullptr; //自己置成空,自己没了,
            }
    
            T &operator*()
            {
                return *_ptr;
            }
            T *operator->() //返回原生指针即可
            {
                return _ptr;
            }
        };
    };
    void demo1()
    {
        int *p1 = new int;
        xzw::auto_ptr<int> sp1(p1); //拷贝构造,出了作用域就调用他们的析构函数,抛异常的话也会出作用域,也就自动调用析构函数,
        int *p2 = new int;
        xzw::auto_ptr<int> sp2(sp1);     //拷贝构造
        xzw::auto_ptr<int> sp3(new int); //直接用new出来的资源给它
        *sp3 = 10;
        cout << __LINE__ << *sp3 << endl;
        cout << *sp1 << endl; //出现空指针问题
        //希望能够像指针一样使用,重载以下operator*
    
        //结论就是auto_ptr是一个失败的设计.很多公司明确要求不能使用auto_ptr
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    实际工作中绝对不能使用auto_ptr

    C++11的智能指针

    定制删除器

    默认情况下,智能指针底层的删除器都是用delete
    但是不同的资源销毁的方式不同,直接用delete十分暴力,不合理,所以就有了定制删除器

    比如:

    • malloc -> free
    • open -> close
    • fopen -> fclose
    • new[] -> delete[]

    我们在下文会详细解释

    unique_ptr的模拟实现

    unique_ptr对于拷贝构造的解决方式即直接把拷贝构造给禁止了

    namespace Uni_Ptr
    {
    
            template <class T>
        class defult_delete
        {
        public:
            void operator()(const T* ptr)
            {
                cout<<__LINE__<<endl;
                cout<<"delete"<<endl;
                delete ptr;
            }
        };
        template <class T, class D=default_delete<T>>//默认释放这个类型,在模板里面调用的不是仿函数,而是对应的类型
    
        //原理简单粗暴,——防拷贝,直接不让你拷贝
        class unique_ptr
        {
        private:
            T *_ptr;
            unique_ptr(const unique_ptr<T> &sp) = delete; //直接把它拷贝给弄掉
        public:
            //在构造函数
            unique_ptr(T *ptr)
                : _ptr(ptr) //原生指针支持拷贝
            {
            }
            ~unique_ptr()
            {
                if (_ptr)
                {
                    // cout << "delete" << endl;
                    // delete _ptr; //析构函数把他清理
                    D del;
                    del(_ptr);//默认的情况就是用default_delete,
                }
            }
    
            T &operator*()
            {
                return *_ptr;
            }
            T *operator->() //返回原生指针即可
            {
                return _ptr;
            }
        };
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    void demo2()
    {
        Uni_Ptr::unique_ptr<int> sp1(new int);
        // Uni_Ptr::unique_ptr sp2(sp1);
        // std::unique_ptr sp(sp1);//不支持拷贝构造
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    unique_ptr对于定制删除器的使用,就是我们在外面写一个类的仿函数,在模板里面进行传参即可

    template <class T>
    struct DeleteArray
    {
        void operator()(const T *ptr)
        {
            cout << "delete[]" << endl;
            delete[] ptr;
        }
    };
    
    #include
    #include
    struct DeleteFile
    {
        void operator()(FILE *ptr)
        {
            cout << "fclose:" << endl;
            fclose(ptr);
        }
    };
    
    
    void demo6()
    {
        //定制删除器
        //默认情况下,智能指针在底层都是用delete
        //那么如果不是new 出来,如new[],malloc,fopen
        //unque_ptr是在类的模板参数里面(类型)
        Uni_Ptr::unique_ptr<Date> s(new Date);
        Uni_Ptr::unique_ptr<Date, DeleteArray<Date>> s1(new Date[10]); //我们可以显示定制删除器
        Uni_Ptr::unique_ptr<FILE,DeleteFile> s2(fopen("1.txt","w"));//我们这里用fopen,要自己特制一个删除器
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    shared_ptr的模拟实现

    shared_ptr是为了解决unique_ptr无法实现拷贝构造
    新增加了一个引用计数的机制:

    同一个对象只能有一个引用计数,当调用构造函数的时候,第一个引用计数出现为1,后续如果有发生拷贝构造,引用计数就+1,当析构的时候,引用计数就-1,直到引用计数为0的时候,这个资源就销毁

    即由最后一个管理的对象来进行对资源的释放

    • 智能指针是线程安全的,因为对里面的引用计数进行了加锁处理,但是指向的资源不是线程安全的,这个需要使用者手动处理
    namespace Shared_Ptr
    {
        //我们要实现定制删除器
    
        template <class T>
        class defult_delete
        {
        public:
            void operator()(const T* ptr)
            {
                cout<<__LINE__<<endl;
                delete ptr;
            }
        };
        template <class T, class D=default_delete<T>>//默认释放这个类型,在模板里面调用的不是仿函数,而是对应的类型
        class shared_ptr
        {
        private:
            T *_ptr;
            // static int _refcout; //这样就只有一个了,属于所有对象共享,但是要在类外面初始化,所以我们应该一个资源配一个引用计数,因为static是属于类的
            int *_pRefCount; //这个就是引用计数,凡是用浅拷贝都是要用引用计数,才能搞定这个东西
            //一个引用计数就要管理一个锁
            mutex *_mtx; // 这样就可以访问同一个锁
        public:
            //在构造函数,调用一次构造函数引用计数就加1
            shared_ptr(T *ptr)
                : _ptr(ptr), _pRefCount(new int(1)), _mtx(new mutex) //原生指针支持拷贝,用指针,不同的人就可以指向同一个资源
    
            {
                //只有第一个人会调用构造函数
                // _refcout=1;//就为1,管理不同的资源就会把别人的给修改了,
            }
    
            shared_ptr(const shared_ptr<T> &sp)                           //调用拷贝构造
                : _ptr(sp._ptr), _pRefCount(sp._pRefCount), _mtx(sp._mtx) //引用计数也拷贝过去
            {
                // ++_refcout;
                // ++(*_pRefCount); //因为是同一个资源,所以就可以对它进行++,
                AddRef();
            }
            ~shared_ptr()
            {
                Release();
            }
            T *get() const
            {
                return _ptr;
            }
            //如何解决拷贝问题,管理权转移,只有一个人析构它
    
            T &operator*()
            {
                return *_ptr;
            }
            T *operator->() //返回原生指针即可
            {
                return _ptr;
            }
            void Release()
            {
                _mtx->lock();
                //管理临界区
                (*_pRefCount)--; //析构的时候,把引用计数--
                bool flag = false;
                if (!(*_pRefCount) && _ptr)
                {
                    cout << "delete" << endl;
                    delete _ptr; //析构函数把他清理
                    delete _pRefCount;
                    _ptr = nullptr;
                    _pRefCount = nullptr;
                    flag = true;
                    // delete是把原来对应的空间给释放掉,但是对应的指针还在堆区里面,所以还能用
                }
                _mtx->unlock();
                if (flag)
                {
                    delete _mtx;
                    _mtx = nullptr;
                }
            }
            void AddRef()
            {
                _mtx->lock();
                (*_pRefCount)++;
                _mtx->unlock();
            }
            shared_ptr<T> &operator=(const shared_ptr<T> &sp) //这个就牵扯到两个资源,
            {
                if (_ptr != sp._ptr) //自己给自己赋值
                {
                    //原来的引用计数减一
                    Release();
                    _ptr = sp._ptr;             // delete之后指针还在,指向不同的空间就行了
                    _pRefCount = sp._pRefCount; //现在引用计数也要和其相等
                    _mtx = sp._mtx;
                    AddRef();
                }
                return *this;
            }
        };
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    void demo3()
    {
        Shared_Ptr::shared_ptr<int> sp(new int);
        Shared_Ptr::shared_ptr<int> sp1(sp);
        Shared_Ptr::shared_ptr<int> sp2(sp);
        Shared_Ptr::shared_ptr<int> sp3(sp);
        Shared_Ptr::shared_ptr<int> sp4(new int(3)); //这样就可以做到构造的对象就只析构一次
        sp1 = sp4;
        sp2 = sp4;
        sp = sp4;
        // sp3=sp4;
    
        //指向的堆上资源的线程安全的问题是访问的人处理的,智能指针不管
        //引用计数的线程安全问题是智能指针要处理的
    
        *sp4 = 3;
        *sp = 4;
    
        cout << (*sp3) << endl;
        cout << __LINE__ << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    struct Date
    {
        int _year = 1;
        int _month = 1;
        int _day = 1;
    };
    
    void SharePtreFunc(Shared_Ptr::shared_ptr<Date> &sp, size_t n, mutex &mtx)
    {
        for (int i = 0; i < n; i++)
        {
            Shared_Ptr::shared_ptr<Date> copy(sp);
            //访问临界资源,加锁
    
            {
                //这里我们可以括一个匿名域
                unique_lock<mutex> lock(mtx); //这个支持中间解锁
    
                copy->_day++;
                copy->_month++;
                copy->_year++;
                //或者unlock
                lock.unlock();
            }
    
            cout << "hello" << endl; //这个我不想管它的线程安全
        }
    }
    
    //智能指针是线程安全的
    //因为引用计数的加减都是加锁保护的,但是指向的资源不是线程安全的,要我们自己手动处理
    
    void demo4()
    {
        Shared_Ptr::shared_ptr<Date> p(new Date);
        const size_t n = 10000000;
        mutex mtx;
        thread t1(SharePtreFunc, std::ref(p), n, std::ref(mtx)); //这样就有线程安全问题,两个线程同时去++,就会有问题,
        thread t2(SharePtreFunc, std::ref(p), n, std::ref(mtx));
    
        t1.join();
        t2.join();
        cout << p->_day << endl;
        cout << p->_month << endl;
        cout << p->_year << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    shared_ptr对于定制删除器的使用:就是在构造函数的时候加上,该可调用对象(函数指针,仿函数,lambda表达式)
    使用lambda表达式最方便

    template <class T>
    struct DeleteArray
    {
        void operator()(const T *ptr)
        {
            cout << "delete[]" << endl;
            delete[] ptr;
        }
    };
    
    #include
    #include
    struct DeleteFile
    {
        void operator()(FILE *ptr)
        {
            cout << "fclose:" << endl;
            fclose(ptr);
        }
    };
    
    
    void demo6()
    {
    
    
        //删除器在构造函数里面给对象
        std::shared_ptr<Date> srp(new Date);
        std::shared_ptr<Date> srp4(new Date[10],DeleteArray<Date>());
        
        //使用lambda表达式就更加方便了
    
        std::shared_ptr<Date> srp3(new Date[10],[](Date* ptr){cout<<"delete []"<<endl;
        delete[] ptr;});
        
        std::shared_ptr<FILE> srp1(fopen("1.txt","w"),DeleteFile());//在构造函数里面传删除器
        std::shared_ptr<FILE> srp2(fopen("1.txt","w"),[](FILE* ptr){fclose(ptr);});//在构造函数里面传删除器
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    weak_ptr

    shared_ptr会出现循环引用的问题
    一个指针,你里面有一个智能指针,指向我,我里面有一个智能指针指向你,这就是循环引用

    struct ListNode
    {
        int _val;
    
         shared_ptr<ListNode> _prev;
         shared_ptr<ListNode> _next;
    };
    
    void demo5()
    {
        shared_ptr<ListNode> n1(new ListNode);
        shared_ptr<ListNode> n2(new ListNode);
        cout << n1.use_count() << endl;
        cout << n2.use_count() << endl;
        //这样就能解决循环引用的问题,一个指针,你里面有一个智能指针,指向我,我里面有一个智能指针指向你,这就是循环引用
    
         n1->_prev=n2;//这样子会增加引用计数
         n2->_next=n1;
        cout << n1.use_count() << endl;
        cout << n2.use_count() << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    这样子会出现问题

    weak_ptr是一个弱指针,没有引用计数的机制,可以支持shared_ptr对它进行拷贝构造和赋值

      template <class T>
        class weak_ptr
        {
        private:
            T *_ptr;
    
        public:
            weak_ptr()
                : _ptr(nullptr)
            {
            }
            weak_ptr(const shared_ptr<T> &sp)
                : _ptr(sp.get())
            {
            }
            weak_ptr<T> &operator=(const shared_ptr<T> &sp)
            {
                _ptr = sp.get();
                return *this;
            }
        };
    	
    struct ListNode
    {
        int _val;
        Shared_Ptr::weak_ptr<ListNode> _prev; //弱指针,不增加引用计数,只是可以访问指向的节点资源,但是不参与节点资源的释放管理,
        Shared_Ptr::weak_ptr<ListNode> _next;
        
    };
    
    
    void demo5()
    {
        shared_ptr<ListNode> n1(new ListNode);
        shared_ptr<ListNode> n2(new ListNode);
        cout << n1.use_count() << endl;
        cout << n2.use_count() << endl;
        //这样就能解决循环引用的问题,一个指针,你里面有一个智能指针,指向我,我里面有一个智能指针指向你,这就是循环引用
    
         n1->_prev=n2;
         n2->_next=n1;
        cout << n1.use_count() << endl;
        cout << n2.use_count() << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    总结一点:如果使用对象的话,就要用强指针,如果要引用一个对象的话,就要用弱指针
    即weak_ptr不会参与空间资源的管理,只是作为一个解决循环引用的工具

  • 相关阅读:
    C语言等长编码压缩和哈夫曼编码压缩
    Keras CIFAR-10分类 SVM 分类器篇
    A tour of gRPC:05 - gRPC server straming 服务端流
    Vue:生命周期(从出生到销毁)
    BOM操作——window对象(一)
    SSL证书对于SEO优化的重要性
    R语言替换字符串中指定字符的子串:sub函数查找字符串中第一个匹配到的子串并替换、如果要删除指定字符串子串则将替换的子符串设置为空字符串
    java8 Lambda表达式以及Stream 流
    力扣:1175. 质数排列
    SSM+Vue+Element-UI实现大学生心理健康系统
  • 原文地址:https://blog.csdn.net/m0_61567378/article/details/126488360