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
}

实际工作中绝对不能使用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;
        }
    };
};

void demo2()
{
    Uni_Ptr::unique_ptr<int> sp1(new int);
    // Uni_Ptr::unique_ptr<int> sp2(sp1);
    // std::unique_ptr<int> sp(sp1);//不支持拷贝构造
}

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

template <class T>
struct DeleteArray
{
    void operator()(const T *ptr)
    {
        cout << "delete[]" << endl;
        delete[] ptr;
    }
};

#include<cstdio>
#include<stdio.h>
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,要自己特制一个删除器

}

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;
        }
    };
    };
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;
}
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;
}

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

template <class T>
struct DeleteArray
{
    void operator()(const T *ptr)
    {
        cout << "delete[]" << endl;
        delete[] ptr;
    }
};

#include<cstdio>
#include<stdio.h>
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);});//在构造函数里面传删除器

}

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;
}

这样子会出现问题

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;
}

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340