智能指针的使用以及模拟实现

文章目录

  • 智能指针
    • 内存泄漏
    • 智能指针的使用和原理
  • C++98/C++11智能指针
    • auto_ptr
    • unique_ptr
    • shared_ptr
    • weak_ptr
  • 删除器
  • 总结


智能指针

在C语言中,我们初识了指针这一概念,在C++中,我们不断对于指针进行研究和使用,指针的存在是C/C++的一大特色,但是由于非规范操作,可能会导致指针并没有被释放,从而导致了内存泄漏、野指针等问题。

C++中为了解决这一问题,推出了智能指针这一概念

在C++98中推出了auto_ptr,第一代智能指针,来进行对于指针资源管理

在C++11中相继推出三种智能指针,unique_ptr、shared_ptr、weak_ptr

在学习异常之前,我们知道出现内存泄漏一般是由于指针不规范使用导致,但是异常的出现,可跳出当前作用域这一行为,也可以导致内存泄漏的发生。

//智能指针
void Add()
{
    int* ptr = new int[10];//我们在堆上动态开辟空间
    //如果上述ptr开辟空间失败,抛异常,被func中的catch抓住,就不会执行delete ptr
    delete ptr;
}
void func()
{
    try {
        Add();
    }
    catch(const exception& e)
    {
        cout << e.what() << endl;//输出异常
    }
}
int main()
{
    func();
    return 0;
}

智能指针的使用以及模拟实现_第1张图片

所以,我们就算有时候操作很规范,也会可能导致内存泄漏,智能指针的出现就会解决这一问题

内存泄漏

我们先介绍一些内存泄漏的危害性,在工程项目中,如果出现了内存泄漏,且每一次泄漏一小部分,很难被察觉,那么就会在启动该项目的一周,一个月后,发现该项目的运行越来越卡顿,最后项目崩溃。

如果内存泄漏很慢,这是最难被发现的

如果泄漏很快,说不定项目启动当天就能发现,从而解决

由内存泄漏导致出现的项目崩溃,是为事故,轻则奖金扣除,总则打包回家!!!

C/C++程序中主要是两个方面可能会出现内存泄漏,堆内存泄漏和系统资源泄漏

堆内存泄漏

堆内存泄漏,就是我们在程序中使用malloc/calloc/realloc/new等关键字从堆中进行分配内存资源,在该资源使用完毕后,没有合理使用free/delete等删掉该资源(释放资源),从而导致的内存泄漏。

系统资源泄漏

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

规避内存泄漏方法

1.规范使用代码(但是异常还是可能会有内存泄漏的风险)

2.使用RAII思想或者智能指针来进行管理资源(申请和释放)

3.使用第三方检测工具来实现对于程序的内存泄漏的检查

智能指针的使用和原理

智能指针的实现,使用了RALII的原理,即利用对象生命周期来控制程序资源的思想,用类进行包装,从而通过类自带的构造和析构函数,实现对于资源的申请和释放,从而避免内存泄漏

在对象构造的时候,进行资源的申请;在对象析构的时候,对资源进行释放

namespace why{
    //设计实现简单的智能指针
    template<class T>
    class auto_ptr
    {
    public:
        auto_ptr(T* ptr=nullptr)
            :_ptr(ptr)
        {
            //ptr = new int;//实现在构造的时候,申请资源
            cout << "构造函数,申请资源" << endl;
        }
        ~auto_ptr()
        {
            cout << "析构函数,释放资源" << endl;
            delete _ptr;//在析构的时候,释放资源
        }
        T& operator*()
        {
            return *_ptr;
        }
        T* operator->()
        {
            return _ptr;
        }
    private:
        T* _ptr;
    };

}

int main()
{
    why::auto_ptr<string> aaa;//默认构造
    why::auto_ptr<int> a(new int);//在构造的时候进行申请资源
    auto_ptr<string> a(new string("xxxx"));
        
    return 0;
}

C++98/C++11智能指针

auto_ptr

c++98中推出的智能指针,可以对于资源进行管理,特点是可以实现管理权的转移(转移后,将被转移的指针指向为nullptr),但是在某种情况下会出现野指针的风险

int main()
{
    //使用智能指针auto_ptr
    auto_ptr<string> ptr1(new string("xxx"));
    
    auto ptr2 = ptr1;
    cout << ptr1.get() << endl;
    cout << ptr2.get() << endl;//get函数,得到二进制地址
    cout << *ptr1 << endl;
    return 0;
}

智能指针的使用以及模拟实现_第2张图片

所以说,auto_ptr在设计上并不完善,对于上述情况,会造成的野指针报错,从而,不推荐使用auto_ptr

模拟实现基本的auto_ptr功能

namespace why
{   //模拟实现auto_ptr
    template<class T>
    class auto_ptr
    {   
    public:
        auto_ptr(T* ptr=nullptr)
            :_ptr(ptr)
        {}
        T& operator*()
        {
            return *_ptr;
        }
        T& operator->()
        {
            return _ptr;
        }

        auto_ptr(const auto_ptr<T>& ptr)
            :_ptr(ptr._ptr)
        {
            delete ptr._ptr;
            //ptr._ptr = nullptr;
        }
        ~auto_ptr()
        {
            delete _ptr;
        }
    private:
        T* _ptr;
    };
}

unique_ptr

unique_ptr是在c++11之后推出的智能指针,除了不能赋值拷贝,和auto_ptr功能了类似

智能指针的使用以及模拟实现_第3张图片

namespace why
{
    template<class T>
    class unique_ptr
    {
    public:
        unique_ptr(T* ptr=nullptr)
            :_ptr(ptr)
        {}

        T& operator*()
        {
            return *_ptr;
        }
        unique_ptr(const unique_ptr<T>& up) = delete;//防止赋值拷贝,以及赋值拷贝构造
        unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
        T* operator->()
        {
            return _ptr;
        }
        ~unique_ptr()
        {
            delete _ptr;
        }
    private:
        T* _ptr;
    };
}
int main()
{
    why::unique_ptr<string> ptr1(new string("xxx"));
    //why::unique_ptr ptr2;
    //ptr2 = ptr1;  //赋值拷贝
    // 
    auto ptr3 = ptr1;//赋值拷贝+构造=拷贝构造,编辑器优化直接调用拷贝构造
    //auto ptr2(ptr1);
    return 0;
}

智能指针的使用以及模拟实现_第4张图片

shared_ptr

C++11推出的智能指针,其特点是可以多个指针对象共享一块资源(指向同一空间),是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

namespace why
{
    template<class T>
    class shared_ptr
    {
    public:
        shared_ptr(T* ptr=nullptr)
            :_ptr(ptr)
            ,_pnum(new int(1))//初始计数为1,表示有一个指针指向这一空间
        {
            cout << "构造函数的实现" << endl;
        }
        T& operator*()
        {
            return &_ptr;
        }
        T* operator->()
        {
            return _ptr;
        }
        shared_ptr(const shared_ptr<T>& sp)//拷贝构造函数
        {
            cout << "拷贝构造函数的实现" << endl;
            if (this != &sp)
            {
                _ptr = sp._ptr;
                _pnum = sp._pnum;
                ++(*_pnum);
            }
        }
        shared_ptr<T> operator=(const shared_ptr<T> sp)//赋值拷贝是先执行拷贝构造,然后再执行赋值
        {
            cout << "赋值拷贝的实现" << endl;
            if (this == &sp)
            {
                return *this;
            }
            if (--(*_pnum) == 0)
            {
                delete _ptr;
                delete _pnum;
            }
            _ptr = sp._ptr;
            _pnum = sp._pnum;
            ++(*_pnum);

            return *this;
        }
        ~shared_ptr()
        {
            if (--(*_pnum) == 0)//如果为0,表示直接释放即可,如果不为零,也会减去1
            {
                cout << "析构函数的实现" << endl;
                delete _ptr;
                delete _pnum;
            }
        }
        int getPcount()
        {
            return *_pnum;
        }
        
    private:
        T* _ptr;
        int* _pnum;//用来计数,记录指向同一地址的指针,有几个,防止多次对于同一地址进行释放空间,发生异常
    };
}

int main()
{
    why::shared_ptr<string> sp(new string("Xxx"));
    auto sp2(sp);
    why::shared_ptr<string> sp3;
    sp3 = sp2;
    cout << sp2.getPcount() << endl;//设置方法来获得此时共同管理的对象个数

    return 0;
}

shared_ptr的实现,使得可以多个指针共同访问同一地址,相对于unique_ptr区别就是能不能多个对象对于同一空间进行资源管理。

但是对于shared_ptr也存在缺点,可能会导致死循环(不能释放空间),如下代码所示

class node
{
public:
    //假如A类的成员变量中需要指针的存在,且需要多个对象能访问同一空间地址,选择shared_ptr
    shared_ptr<node> prev;
    shared_ptr<node> next;
};
int main()
{
    //如果此时我们想要满足实现类似于链表之间的关系
    shared_ptr<node> a(new node);
    auto b = a;
    a->next = b;
    b->prev = a;

    cout << b.get() << endl;
    cout << a.get() << endl;
    
    return 0;
}

智能指针的使用以及模拟实现_第5张图片

为了解决shared_ptr指针循环引用的问题,引入weak_ptr

weak_ptr

weak_ptr拥有的功能和shared_ptr类似,只是不采取引用计数,即不会去管理指针的生命周期问题,只是单纯的去使用,访问该资源,不会去管理。

weak_ptr一般是配合shared_ptr来使用的一种智能指针,通过这一指针,我们可以解决shared_ptr循环引用的问题

class node
{
public:
    weak_ptr _prev;//不参与管理,不会采取引用计数的方式,所以就算是指向下一个节点,也不会使得计数++
    weak_ptr _next;
};

int main()
{
    shared_ptr a;
    shared_ptr b;
    a->_next = b;
    b->_prev = a;//此时,虽然互相指向,但是a和b的引用计数use_count都是1,即可以直接释放
    return 0;
}

weak_ptr还有很多其他的作用和功能,但是其主要是配合shared_ptr来使用

use_count 函数得到引用计数的数值

expired 函数判断当前weak_ptr指向的shared_ptr是否被销毁,返回类型是bool,判断依据是use_count==0是否成立

lock 函数是如果expired == true,表示weak_ptr管理的shared_ptr已经被销毁,返回nullptr,反之返回指向的shared_ptr

删除器

对于new申请的空间,我们可以通过delete进行直接删除,对于malloc、calloc、realloc等动态申请的空间,我们引入另一种概念,删除器

智能指针的使用以及模拟实现_第6张图片

template<class T>
struct func {
public:
    void operator()(T* t)
    {
        cout << "free" << endl;
        free(t);
    }
};

template<class T>
struct arrdelete {
    void operator()(T* t)
    {
        cout << "delete" << endl;
        delete[] t;
    }
};
int main()
{
    func<string> f;
    arrdelete<string> a;
    //shared_ptr sp1(new string("xxx"), f);
    shared_ptr<string> sp2(new string[10]{"xxx"}, a);//仿函数实现
    shared_ptr<string> sp3(new string[10]{ "Xxxx" }, [](string* ptr) {delete[] ptr; });//lambda函数实现
    return 0;
}

所以如果是对于malloc、calloc、realloc等动态申请空间的指针,我们对于其进行删除管理的时候,需要使用仿函数/lambda函数,lambda函数是最为方便的。

shared_ptr s(new string[10]);

这一种申请方式会编辑报错,需要设计删除器材,delete[]删除,或者是free函数删除(malloc、calloc、realloc函数申请的空间资源),建议使用下面的方式来实现删除器

shared_ptr<string> s(new string[10], [](string* s) {delete[] s; });

shared_ptr<string> s((string*)malloc(4), [](string* s) {free(s); });

总结

智能指针的出现,在项目中具有重要意义,可以自动的控制对象的声明周期,合理使用智能指针就不会出现内存泄漏的问题,在平时练习c++的过程中对于智能指针的要求并不高,可以直接使用普通指针即可。

在智能指针中,auto_ptr已经被抛弃了(风险高),主要是使用c++11之后推出的三种指针,unqiue_ptr、shared_ptr、weak_ptr,进行对于管理动态申请的空间/对象,但是要注意的是对于malloc、calloc、realloc申请的空间要实现删除器,不然会导致运行报错。

//如果是内置类型,就不需要删除器的存在
shared_ptr<string*> s(new string*(nullptr));//如果泛型类型是指针,那么就不需要删除器的存在
shared_ptr<string> s(new string[10]);//对象s为string类型的指针,我们new出来10个类型为string的数组,将地址给s的_ptr,这一种情况就需要删除器的存在,以为T类型为string  进入析构函数为delete _ptr,不能删除数组地址,所以会运行报错

删除器的存在与否,决定于泛型中T的类型,析构函数为delete _ptr(T*_ptr) 是否能删除new出来或者malloc的空间

即delete _ptr是否能匹配

shared_ptr<string> s(new string[10]);
//string* _ptr   -> 构造函数 -> _ptr=new string[10];
//析构函数 -> delete _ptr ->delete 数组应该为delete[]_ptr 所以不能匹配,不能正确释放,即运行报错

//所以是否需要删除器,关键是T的类型和delete _ptr以及构造函数的参数(上文的new string[10])

你可能感兴趣的:(C++,智能指针,C++,模拟实现)