当前位置 : 主页 > 编程语言 > c语言 >

C++11之智能指针(万字长文详解)

来源:互联网 收集:自由互联 发布时间:2023-09-14
C++11之智能指针 为什么需要智能指针 #include iostreamusing namespace std;int div(){ int a, b; cin a b; if (b == 0) throw invalid_argument(除0错误); return a / b;}void Func(){ // 1、如果p1这里new 抛异常会如何? //

C++11之智能指针

为什么需要智能指针

#include <iostream>
using namespace std;
int div()
{
       int a, b;
       cin >> a >> b;
       if (b == 0)
           throw invalid_argument("除0错误");
       return a / b;
}
void Func()
{
    // 1、如果p1这里new 抛异常会如何?
    // 2、如果div调用这里又会抛异常会如何?
       int* p1 = new int[10];
       cout << div() << endl;//如果div抛出异常那么就会导致内存泄漏!
       //因为后面的delete无法执行!会直接跳出这个函数
       delete[] p1;
}
int main()
{
       try
       {
           Func();
       }
       catch (const exception& e)
       {
           cout << e.what() << endl;
       }
       return 0
}

解决办法 一

void Func()
{
       int* p1 = new int[10];
       try
       {
           cout << div() << endl;
       }
       catch (...)
       {
           delete[]p1;
           throw;
       }
       delete[]p1;
}

==但是这样子虽然解决了,但是首先这个代码不整洁和美观,其次,这个代码还有一个问题==

void Func()
{
       int* p1 = new int[10];
       int* p2 = new int[10];
       try
       {
           cout << div() << endl;
       }
       catch (...)
       {
           delete[]p1;
           delete[]p2;
           throw;
       }
       delete[]p1;
       delete[]p2;
}

这个为什么有问题?——因为new本身也有可能会抛出异常!如果p1抛出异常还好!==但是如果抛出异常的是p2呢?——那么p1内存就无法被释放导致内存泄漏!因为p2抛出异常是直接回到main函数的!既不会执行下面的delete代码,也不会进入下面的catch!==

void Func()
{
    // 1、如果p1这里new 抛异常会如何?
    // 3、如果div调用这里又会抛异常会如何?
       int* p1 = new int[10];
       int* p2 = nullptr;
       try
       {
           p2 = new int[10];
           try
           {
               cout << div() << endl;
           }
           catch (...)
           {
               delete[]p1;
               delete[]p2;
               throw;
           }
       }
       catch (...)
       {
           delete[]p1;
           throw;
       }
       delete[]p1;
       delete[]p2;
}

但是这还只是两个new,如果是三个呢,四个呢?

==所以为了解决这种类似的情况!于是有了智能指针!==

智能指针的原理

==智能指针的原理其实很简单!——我们不要手动释放!让指针出了作用域后自动的释放!==

那么如何做到的?——写一个类就好了!

template<class T>
class SmartPtr
{
public:
       //构造函数在保存资源!
       SmartPtr(T* ptr = nullptr)
           :_ptr(ptr)
           {}

       //析构函数在释放资源!
       ~SmartPtr()
       {
           if (_ptr)
               delete _ptr;
           cout << "~SmartPtr()" << endl;
       }
       //重装*  让智能指针具有像一般指针一样的行为
       T& operator*()
       {
           return *_ptr;
       }
    
       T* operator->()
       {
           return _ptr;
       }
       T& operator[](size_t pos)
       {
           return _ptr[pos];
       }
private:
       T* _ptr;
};

==我们都知道出了作用域后,类会自动的去调用析构函数去释放资源!我们就可以通过这一特性来实现智能指针!==

#include <iostream>
using namespace std;
int div()
{
       int a, b;
       cin >> a >> b;
       if (b == 0)
           throw invalid_argument("除0错误");
       return a / b;
}
void Func()
{
       int* p1 = new int[10];
       SmartPtr<int> sp1(p1);
       int* p2 = new int[10];
       SmartPtr<int> sp2(p2);
       *p1 = 10;
       p1[0]--;
       cout << div() << endl;
}

int main()
{
       try
       {
           Func();
       }
       catch (const exception& e)
       {
           cout << e.what() << endl;
       }
       return 0;
}

image-20230523120918530

如果p2抛异常,p1出了作用域也会自动释放!不用担心!

RAII

上面的这种思想我们称之为RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。

Resource Acquisition Is Initialization 意思就是资源获取即初始化!——这个初始化就是构造函数!(就是说当获取到一个需要释放了资源的时候,不要自己手动管理!而是通过一个对象进行管理!当和对象绑定后,这个资源就和对象的生命周期绑定了!)

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在 对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做 法有两大好处:

  • 不需要显式地释放资源
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。

==智能指针分为两个部分——一个就是RAII,一个值像指针的行为!==

template<class T>
class SmartPtr
{
    public:
       //*******************************************************
       //这一部分就是RAII
       //构造函数在保存资源!
       SmartPtr(T* ptr = nullptr)
           :_ptr(ptr)
           {}

       //析构函数在释放资源!
       ~SmartPtr()
       {
           if (_ptr)
               delete _ptr;
           cout << "~SmartPtr()" << endl;
       }
       //**************************************************************
       //第二部分
       //重装*  让智能指针具有像一般指针一样的行为
       T& operator*()
       {
           return *_ptr;
       }
    
       T* operator->()
       {
           return _ptr;
       }
       T& operator[](size_t pos)
       {
           return _ptr[pos];
       }
    private:
       T* _ptr;
};

STL中的智能指针

STL库中给我们提供了四种不同类型的智能指针!分别是auto_ptr,unique_ptr,weak_ptr,shared_ptr接下来将会一一介绍

都在momory这个头文件里面!

std::auto_ptr

auto_ptr是STL库中最早引入的智能指针!也是最臭名昭著的智能指针!——它具有十分的多的缺陷!

template<class T>
class SmartPtr
{
public:
       SmartPtr(T* ptr = nullptr)
           :_ptr(ptr)
           {}
       ~SmartPtr()
       {
           if (_ptr)
               delete _ptr;
       }
       T& operator*()
       {
           return *_ptr;
       }

       T* operator->()
       {
           return _ptr;
       }
       T& operator[](size_t pos)
       {
           return _ptr[pos];
       }
private:
       T* _ptr;
};
//这是我上面写的智能指针!——它有一个很大的问题!——拷贝!
int main()
{
       SmartPtr<int> sp(new int);
       SmartPtr<int> sp2(sp);
       return 0;
}

image-20230524144632999

因为默认生成的拷贝构造是一个浅拷贝!这就是导致了sp与sp2都是维护的是同一个的地址!所以一旦出了作用域!就会导致同一块空间被释放两次!

解决办法很多——例如写一个计数器!

==这里不可以深拷贝!我们模拟的是原生指针的行为!两个指针指向之间的拷贝本身就是深拷贝!==

==但是auto_ptr解决这个的问题的方式是——所有权的转移!==

//这是一种非常荒唐的解决办法
#include <memory>
using namespace std;

int main()
{
    auto_ptr<int> ap(new int);
    auto_ptr<int> ap1(ap);
    return 0;
}

image-20230524145931209

==这个就会导致如果不知道的人会直接对空指针进行解引用!——即对象悬空问题!==

==赋值也是一样的!也会导致管理权的转移!==

==所以不要去使用!auto_ptr!这是一个非常危险的东西==

auto_ptr的底层实现
namespace MySTL 
{
       template<class T>
       class auto_ptr
       {
        public:
           auto_ptr(T* ptr = nullptr)
               :_ptr(ptr)
               {}
           ~auto_ptr()
           {
               if (_ptr)
                   delete _ptr;
           } 

           auto_ptr(auto_ptr<T>& ap)//不可以加上const,这会导致ap._ptr = nullptr;编译不通过
               :_ptr(ap._ptr)
               {
                   ap._ptr = nullptr;
               }

           T &operator*() {
               return *_ptr;
           }

           T *operator->() {
               return _ptr;
           }

       private:
           T *_ptr;
       };
}

==auto_ptr的实现很简单,就是一个简单的置空就好了==

unique_ptr

这个智能指针的前身是boost库里面的scope_ptr,后续进入了STL库中改名为unique_ptr

==unique_ptr对于解决拷贝的方式非常的简单粗暴!==

int main()
{
       unique_ptr<int> up1(new int(10));
       unique_ptr<int> up2(up1);
       up2 = up1;
       return 0;
}

image-20230524152324937

它直接禁掉了拷贝构造和赋值!不让拷贝构造也不让赋值!——就像是它的名字一样unique(唯一)

unique_ptr的底层实现
template <class T>
class unique_ptr
{
   public:
       unique_ptr(T* ptr = nullptr)
           :_ptr(ptr)
           {}

       ~unique_ptr()
       {
           if (_ptr)
               delete _ptr;
       }
   
       //直接全部禁掉!
       unique_ptr(unique_ptr<T>& up) = delete;
       unique_ptr<T>& operator=(unique_ptr<T>& up) = delete;
   
       T &operator*() {
           return *_ptr;
       }
   
       T *operator->() {
           return _ptr;
       }
   
private:
       T *_ptr;
   };

shared_ptr

不让拷贝终究不能解决问题,所以就有了shared_ptr——这个智能指针的解决办法就是使用==引用计数!==

int main()
{
       shared_ptr<int> sp1(new int(10));
       shared_ptr<int> sp2(sp1);
       (*sp1)++;
       cout << *sp1 << endl;
       cout << &(*sp1) << endl;
       cout << &(*sp2) << endl;
       return 0;
}

image-20230524153527765

==我们可以看到就可以进行正常的拷贝了!==

引用计数的实现

首先我们要先看一下引用计数应该如何实现

template<class T>
class shared_ptr
{
       //...
       //
   private:
       T *_ptr;
       size_t count;//这样写就是一个典型的错误!
};

==为什么我们不能怎么写!因为这样子计数器就是互相独立的!==

image-20230524155235384

==所以我们需要一个统一的计数器!==

template<class T>
class shared_ptr
{
       //...
       //
   private:
       T *_ptr;
       static size_t count;//既然如此我们搞一个静态的行不行?
};

image-20230524161154842==这就导致了只能管理一个内存块!而不是多个内存块!我们如果释放sp1,sp2后但是因为count不为0所以就不会释放!它们指向的内存块!这就已经造成了内存泄漏!==

那么我们应该怎么解决这个问题?一个资源必须匹配一个引用计数!

有很多解决的办法——例如我们写一个静态的map,将一个内存地址和计数器当成是kv键值对!

static map<T* ptr,int count>;

不要使用vector< int >,因为这样子不好找是哪个内存块对应哪个计数器

==而在STL库中是使用如下的方法解决的==

image-20230524165922810

==如果某个对象析构就在那个指向的空间--就可以了==

shared_ptr的底层实现
template<class T>
class shared_ptr
{
public:
       shared_ptr(T* ptr = nullptr)
           :_ptr(ptr),
       _pcount(new int(1))
       {}

       shared_ptr(const shared_ptr<T>& sp)
           :_ptr(sp._ptr),
       _pcount(sp._pcount)
       {
           ++(*_pcount);
       }
       ~shared_ptr()
       {
           if(--*(_pcount) == 0)
           {
               delete _ptr;
               delete _pcount;
           }
       }
       T &operator*()
       {
           return *_ptr;
       }

       T *operator->()
       {
           return _ptr;
       }

private:
       T *_ptr;
       int* _pcount;
};
int main()
{
       MySTL:: shared_ptr<int> sp1(new int(10));
       MySTL::shared_ptr<int> sp2(sp1);

       MySTL::shared_ptr<int> sp3(new int(10));
       return 0;
}

image-20230524173724292

==shared_ptr的难点是赋值重装!==

shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
       _ptr = sp._ptr;
       _pcount = sp._count;
       ++(*_pcount);
       return *this;
}

==这样写就是出现问题了!——已经出现内存泄漏了!==

image-20230524180254335

如果我们简单的像上面写,那么就很容易导致内存泄露!

shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
    if(_ptr != sp._ptr)//要考虑到自己给自己赋值
    {
        if(--(*_pcount) == 0)
        {
            delete _ptr;//释放掉这个指针原本的指向的内存
            delete _pcount;
        }
        _ptr = sp._ptr;
        _pcount = sp._pcount;
        ++(*_pcount);//将新指向的代码块的引用计数++
    }
    return *this;
}

==然后我们精简一下代码就变成了如下的==

template<class T>
class shared_ptr
{
public:
    shared_ptr(T* ptr = nullptr)
        :_ptr(ptr),
    _pcount(new int(1))
    {}

    shared_ptr(const shared_ptr<T>& sp)
        :_ptr(sp._ptr),
    _pcount(sp._pcount)
    {
        ++(*_pcount);
    }
    void release()//将相同的部分写成一个函数
    {
        if(--(*_pcount) == 0)
        {
            delete _ptr;
            delete _pcount;
        }
    }
    ~shared_ptr()
    {
        release();
    }
    shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
        if(_ptr != sp._ptr)//要考虑到地址相同的情况赋值!
        {
            release();
            _ptr = sp._ptr;
            _pcount = sp._pcount;
            ++(*_pcount);//将新指向的代码块的引用计数++
        }
        return *this;
    }
    T &operator*()
    {
        return *_ptr;
    }

    T *operator->()
    {
        return _ptr;
    }
private:
    T *_ptr;
    int* _pcount;
};

shared_ptr的缺点

线程安全问题

==如果出现shared_ptr管理的内存被两个线程同时管理!==

class shared_ptr
{
    public:
       //.....
       //我们可以写一个函数来获取_pcount的值
       int use_count()
       {
           return *_pcount;
       }
    private:
       T *_ptr;
       int* _pcount;
};

void test_shared_ptr()
{
       int n = 1000;
       shared_ptr<int> sp(new int(1));
       thread t1([&]() 
                 {
                     for (int i = 0; i < n; ++i)
                     {
                         shared_ptr<int> sp2(sp);
                     }
                 });

       thread t2([&]() 
                 {
                     for (int i = 0; i < n; ++i)
                     {
                         shared_ptr<int> sp3(sp);
                     }
                 });
       t1.join();
       t2.join();
}
int main()
{
       test_shared_ptr();
}

image-20230525170718526

==上面程序运行的结果!——什么都有可能!甚至还有可能报错!==

image-20230525172025808

因为++,--的操作不是原子的!所以这就导致了在多线程的情况下有很强的随机性!

==解决办法!——使用加锁或者使用原子操作的++/--接口!==

template<class T>
class shared_ptr
{
public:
    shared_ptr(T* ptr = nullptr)
        :_ptr(ptr),
    _pcount(new int(1)),
    _pmut(new mutex)
    {}

    shared_ptr(const shared_ptr<T>& sp)
        :_ptr(sp._ptr),
    _pcount(sp._pcount),
    _pmut(sp._pmut)
    {
        _pmut->lock();
        ++(*_pcount);
        _pmut->unlock();
    }
    void release()
    {
        _pmut->lock();
        if(--(*_pcount) == 0)
        {
            delete _ptr;
            delete _pcount;
        }
        _pmut->unlock();
    }
    ~shared_ptr()
    {
        release();
    }
    shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
        if(_ptr != sp._ptr)//要考虑到地址相同的情况赋值!
        {
            release();
            _ptr = sp._ptr;
            _pcount = sp._pcount;
            _pmut = sp._pmut;
            _pmut->lock();
            ++(*_pcount);//将新指向的代码块的引用计数++
            _pmut->unlock();
        }
        return *this;
    }

    T &operator*()
    {
        return *_ptr;
    }

    T *operator->()
    {
        return _ptr;
    }
    int use_count()
    {
        return *_pcount;
    }
private:
    T *_ptr;
    int* _pcount;
    mutex* _pmut;
    //我们这里要使用指针!而不是mutex这个对象!
    //因为我们要保证所有的shared_ptr都在同一个锁下面!
    //如果在不同的锁下面我们就不能保证每一次只会对一个shared_ptr进行++/--了!
};

image-20230525175007665

image-20230525174905822

==这样写代码就可以正常的运行了!==

==使用锁又引入了另一个问题——释放!==

void release()
{
    _pmut->lock();
    if(--(*_pcount) == 0)
    {
        delete _ptr;
        delete _pcount;
        delete _pmut;
    }
    _pmut->unlock();
}

如果直接怎么释放就会出现问题!

==如果我们释放了!那么我们如何解锁?==

image-20230527162753205

我们可以发现会直接崩溃!

==那么我们该如如何去释放呢?——其实也很简单!做一个判断就可以!==

void release()
{
    bool flag = false;//因为这是一个局部变量!所以不用担心线程安全问题!
    _pmut->lock();
    if(--(*_pcount) == 0)
    {
        delete _ptr;
        delete _pcount;
        flag = true;
    }
    _pmut->unlock();
    //解锁完毕之后再进行释放就可以了!
    if (flag)
    {
        delete _pmut;
    }
}

==shared_ptr仅仅保护的是引用计数的线程安全!但是不保证资源的线程安全!==

struct Date
{
    int _year = 0;
    int _month = 0;
    int _day = 0;
};
void test_shared_ptr()
{
    int n = 200000;
    shared_ptr<Date> sp(new Date);
    thread t1([&]() 
              {
                  for (int i = 0; i < n; ++i)
                  {
                      shared_ptr<Date> sp1(sp);
                      sp1->_day++;
                      sp1->_month++;
                      sp1->_year++;
                  }
              });

    thread t2([&]() 
              {
                  for (int i = 0; i < n; ++i)
                  {
                      shared_ptr<Date> sp2(sp);
                      sp2->_day++;
                      sp2->_month++;
                      sp2->_year++;
                  }
              });
    t1.join();
    t2.join();
    cout << sp.use_count() << endl;

    cout<< sp->_day <<endl;
    cout<< sp->_month <<endl;
    cout<< sp->_year <<endl;

}
int main()
{
    test_shared_ptr();
}

image-20230527164357524

==我们可以看到资源的线程安全shared_ptr是无法保证的!因为shared_ptr只能保护里面的数据访问!但是外面的是无法保护的!我们想要让其实线程安全的只能手动的加锁!(或者调用原子类的++/--)==

void test_shared_ptr()
{
    int n = 200000;
    shared_ptr<Date> sp(new Date);
    mutex mux;
    //这也是lambda表达式的一个优势不用进行传参!直接引用捕抓就可以!
    thread t1([&]() 
              {
                  for (int i = 0; i < n; ++i)
                  {
                      shared_ptr<Date> sp1(sp);
                      mux.lock();
                      sp1->_day++;
                      sp1->_month++;
                      sp1->_year++;
                      mux.unlock();
                  }
              });

    thread t2([&]() 
              {
                  for (int i = 0; i < n; ++i)
                  {
                      shared_ptr<Date> sp2(sp);
                      mux.lock();
                      sp2->_day++;
                      sp2->_month++;
                      sp2->_year++;
                      mux.unlock();
                  }
              });
    t1.join();
    t2.join();
    cout << sp.use_count() << endl;

    cout<< sp->_day <<endl;
    cout<< sp->_month <<endl;
    cout<< sp->_year <<endl;
}
int main()
{
    test_shared_ptr();
}

image-20230527164931816

总结

shared_ptr本身是线程安全的!(拷贝和析构,进行引用计数的++/--是线程安全的!)但是==shared_ptr管理资源的访问不是线程安全的!用的时候我们要自己手动加锁保护!==

std库里面的实现的比我们更加的复杂但是也是一样的!

循环引用

shared_ptr的另一个死穴就是循环引用的问题!

struct ListNode
{
       int _data;
       ListNode* _next;
       ListNode* _prev;

       ~ListNode()//写这个意义在于看节点有没有释放!
       {
           cout << "~ListNode()" << endl;
       }
};
void test_shared_ptr_tow()
{
       /* ListNode* n1 =new(ListNode);
 ListNode* n2 =new(ListNode);
 n1->_next = n2;
 n1->_prev = n1;
 delete n1;
 delete n2;*/

       //我们如何将上面的改成智能指针呢?
       shared_ptr<ListNode> spn(new ListNode);
       shared_ptr<ListNode> spn2(new ListNode);

       spn->_next = spn2;
       spn2->_prev = spn;
       //这样写是错误的!因为spn是只能指针类型!
       //但是spn->_next/_prev都是原生指针类型!类型不匹配!  
}

image-20230527170619759

//我们可以修改一下_prev和_next的类型!
struct ListNode
{
       int _data;
       shared_ptr<ListNode> _next;
       shared_ptr<ListNode> _prev;

       ~ListNode()//写这个意义在于看节点有没有释放!
       {
           cout << "~ListNode()" << endl;
       }
};
void test_shared_ptr_tow()
{
       shared_ptr<ListNode> spn(new ListNode);
       shared_ptr<ListNode> spn2(new ListNode);

       spn->_next = spn2;
       spn2->_prev = spn;
}

image-20230527171047998

==我们发现了一个问题!——为什么没有释放?==

void test_shared_ptr_tow()
{
       shared_ptr<ListNode> spn(new ListNode);
       shared_ptr<ListNode> spn2(new ListNode);

       /*spn->_next = spn2;
 	spn2->_prev = spn;*/
       //如果删掉这两句就可以了!
}

image-20230527171315325

==因为刚刚那两句造成了循环引用!(而循环引用则导致了内存泄露的发生!)==

image-20230527173957782

==这就形成了一个死结!_next要被销毁!就要先让 _prev先被销毁! _prev要被销毁首先要 _next先被销毁!==

==只要有两个类!出现两个类里面的对象互相管理着彼此!那么就会导致循环引用的问题!==

解决这个的办法就是我们不要让它==参与管理!指向就好了==

为了解决这个问题于是有了weak_ptr

weak_ptr

==weak_ptr的作用就是解决shared_ptr的循环引用的问题!==

image-20230529111226352

==weak_ptr不支持的指针的构造!说明了weak_ptr不能独立进行管理!——weak_ptr是不支持RAII的!==

但是是支持shared_ptr的构造!

weak_ptr是可以进行指向资源,也可以访问资源!但是不进行管理资源!——不会增加引用计数!

struct ListNode
{
       int _data;
       //std::shared_ptr<ListNode> _next;
       //std::shared_ptr<ListNode> _prev;
       std::weak_ptr<ListNode> _next;
       std::weak_ptr<ListNode> _prev;
       ~ListNode()//写这个意义在于看节点有没有释放!
       {
           cout << "~ListNode()" << endl;
       }
};
void test_shared_ptr_tow()
{
       std::shared_ptr<ListNode> spn(new ListNode);
       std::shared_ptr<ListNode> spn2(new ListNode);

       spn->_next = spn2;
       spn2->_prev = spn;

       cout << spn.use_count() << endl;
       cout << spn2.use_count() << endl;
}

image-20230529112100607

weak_ptr的底层实现
template<class T>
class shared_ptr
{
public:
       //....
       T* get()const
       {
           return _ptr;
       }
       //...
private:
       T *_ptr;
       int* _pcount;
       mutex* _pmut;
};

template<class T>
class weak_ptr
{
public:
       weak_ptr()
           :_ptr(nullptr)
           {}

       weak_ptr(const shared_ptr<T>& sp)//这个是重点!
           :_ptr(sp.get())//因为不在同一个的类里面所我们要在shared_ptr里面写一个get
           {
           }

       weak_ptr<T> operator=(const weak_ptr<T>& wp)
       {
           _ptr = wp._ptr;
           return *this;
       }

       weak_ptr<T> operator=(const shared_ptr<T>& sp)//这个也是重点
       {
           _ptr = sp.get();
           return *this;
       }
       T &operator*()
       {
           return *_ptr;
       }
       T *operator->()
       {
           return _ptr;
       }

private:
       T *_ptr;
       //库里面的也是需要计数的!因为要记录这个weak_ptr是否失效!
       //我们这里只是简略的实现一个!
};

==样子最简单的一个weak_ptr就完成了!==

定制删除器

==上面的我们都没有考虑到一个问题==

share_ptr<int> sp(new int[10]);
//遇到这种情况我们应该怎么办?
//我们里面调用的是delete!但是对于数组我们一般要求delete[] 如果不匹配对于内置类型!
//那么也没有什么问题!如果是自定义类型那么就会导致内存泄漏!甚至会程序崩溃!
//所以我们该如何解决这个问题?

image-20230529130434081

==所以我们需要使用定制删除器来解决这个问题我们可以看一下STL库中的定制删除器是是什么样子==

image-20230529131510343

//定制删除器
template<class T>
struct DeleteArray
{
       void operator()(const T* ptr)
       {
           delete[] ptr;
           cout << "delete[]" << endl;//这个是方便我们用来看的
       }
};

==其实这个定制删除器听上去很高大上!但是本质就是一个仿函数!==

int main()
{
       std::shared_ptr<int> sp1(new int[10],DeleteArray<int>());
       std::shared_ptr<std::string> sp2(new std::string[10],DeleteArray<std::string>());
       std::shared_ptr<std::string> sp3(new std::string[10],
                                        [](const string* ptr)
                                        {delete[] ptr;	cout << "delete[]" << endl;});//或者我们可以使用lambda表达式!(lambda表达式的底层也是一个仿函数)
       return 0;
}

image-20230529131414992

std::shared_ptr<FILE> sp3(fopen("test.txt", "wb"), [](FILE* fp) {fclose(fp); });

==出了上面的的释放数组!还可以去关闭文件!——这就是定制删除器的意义==

定制删除器的实现

==我们在自己的shared_ptr中的是不能像库中在构造函数的时候去传入定制删除器的!因为我们的自己实现的仅仅只是一个类,而库里面其实是由数个类去实现share_ptr的!==

template<class T>
class shared_ptr
{
public:
    //....
    template<class D>
        shared_ptr(T* ptr, D del)//这里有一个很大的问题!
        :_ptr(sp._ptr),
    _pcount(sp._pcount),
    _pmut(sp._pmut),
    _del(del)//这样写看上去没有问题!但是!这个D是属于构造函数的!类成员中如果我们用了D类型就会报错!
    {
        _pmut->lock();
        ++(*_pcount);
        _pmut->unlock();
    }
    //而这个删除器是析构函数要去使用的!
    //所以我们没有办法在构造函数里面传入del
    //库里面是使用另一个类来解决这个问题的!

    //....
private:
    T *_ptr;
    int* _pcount;
    mutex* _pmut;
    D _del;//这个会报错!因为D仅仅属于构造函数!我们无法定义_del
};

==所以我们只能怎么实现==

namesapce MySYL
{
    template<class T>
    struct Defult_delete
    {
        void operator(T* ptr)
        {
            delete ptr;
        }
    }//我们可以写一个默认的模板参数!这样子就可以不用每次都传入了!
    //需要释放数组的时候再传入!而不是用默认的!

    template<class T,class D = Defult_delete<T>>
    class shared_ptr
    {
        public:
        shared_ptr(T* ptr = nullptr)
            :_ptr(ptr),
        _pcount(new int(1)),
        _pmut(new mutex)
        {}

        shared_ptr(const shared_ptr<T,D>& sp)
            :_ptr(sp._ptr),
        _pcount(sp._pcount),
        _pmut(sp._pmut)
        {
            _pmut->lock();
            ++(*_pcount);
            _pmut->unlock();
        }

        void release()
        {
            bool flag = false;
            _pmut->lock();
            if(--(*_pcount) == 0)
            {
                D del;
                del(_ptr);
                delete _pcount;
                flag = true;
            }
            _pmut->unlock();
            if (flag)
            {
                delete _pmut;
            }
        }
        ~shared_ptr()
        {
            release();
        }
        shared_ptr<T,D>& operator=(const shared_ptr<T,D>& sp)
        {
            if(_ptr != sp._ptr)//要考虑到地址相同的情况赋值!
            {
                release();
                _ptr = sp._ptr;
                _pcount = sp._pcount;
                _pmut = sp._pmut;
                _pmut->lock();
                ++(*_pcount);//将新指向的代码块的引用计数++
                _pmut->unlock();
            }
            return *this;
        }

        T &operator*()
        {
            return *_ptr;
        }

        T *operator->()
        {
            return _ptr;
        }
        int use_count()const
        {
            return *_pcount;
        }
        T* get()const
        {
            return _ptr;
        }
        private:
        T *_ptr;
        int* _pcount;
        mutex* _pmut;
    };
}
template<class T>
struct DeleteArray
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
		cout << "delete[]" << endl;
	}
};//手动写一个定制删除器

int main()
{
	MySTL::shared_ptr<int,DeleteArray<int>> sp1(new int[10]);
	MySTL::shared_ptr<std::string, DeleteArray<std::string>> sp2(new std::string[10]);
//	MySTL::shared_ptr<FILE,[](FILE* ptr) {fclose(ptr); } > sp3(fopen("test.cpp", "r"));
    //这样写就是不可以的!因为我们要传的是类型!而lambda表达式其实是一个匿名对象!
//	MySTL::shared_ptr < FILE, decltype([](FILE* ptr) {fclose(ptr); }) > sp3(fopen("test.cpp", "r"));
	//decltype能够推导表达式的类型!那么我们能不能怎么写呢?——不行!因为decltype还在运行的时候推导的!
	//但是模板要求要编译时期就传入类型!
    //所以我们这样实现有很多的缺陷
	return 0;
}

image-20230529224548336

库里面的unique_ptr也是和我们一样使用这种方式来实现定制删除器!同样的也有一样的问题!

image-20230529225500093

struct FClose
{
	void operator()(FILE* ptr)
	{
		fclose(ptr);
		cout << "fclose" << endl;
	}
};
int main()
{
	//std::unique_ptr<FILE, decltype([](FILE* ptr) {fclose(ptr);})> up(fopen("test.cpp","r"));//这样写是错误的!
	std::unique_ptr<FILE, FClose> up2(fopen("test.cpp", "r"));//这样写是可以的!
	return 0;
}

上一篇:3.3 DLL注入:突破会话0强力注入
下一篇:没有了
网友评论