首页 > 编程语言 > C++11之智能指针(万字长文详解)

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

时间:2023-09-13 13:01:02浏览次数:46  
标签:11 return int sp C++ pcount 字长 shared ptr

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

标签:11,return,int,sp,C++,pcount,字长,shared,ptr
From: https://blog.51cto.com/u_15835985/7454574

相关文章

  • Python基础学习day11
    1、文件1.1.控制文件内指针的移动文件内指针移动,只有t模式下的read(n),n代表的字符的个数除此以外文件内指针的移动都是以字节为单位withopen('a.txt',mode='rt',encoding='utf-8')asf:msg=f.read(1)#t模式下的read,按照字符数来移动print(msg)withopen('a.txt',mo......
  • DC-DC升压变换器直流隔离升压模块电源5v12v24v48v转60v80v110v150v220v250v300v500v80
    特点 效率高达80%以上 1*2英寸标准封装 单电压输出 价格低 稳压输出 工作温度:-40℃~+85℃ 阻燃封装,满足UL94-V0要求 温度特性好 可直接焊在PCB上应用HRBW2~40W系列模块电源是一种DC-DC升压变换器。该模块电源的输入电压分为:4.5~9V、9~18V、及18~36V、36~72VDC标准(2......
  • [代码随想录]Day43-动态规划part11
    题目:123.买卖股票的最佳时机III思路:达到dp[i][1]状态,有两个具体操作:操作一:第i天买入股票了,那么dp[i][1]=dp[i-1][0]-prices[i]操作二:第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1]=dp[i-1][1]那么dp[i][1]究竟选dp[i-1][0]-prices[i],还是dp[i-1][1]呢?......
  • CF1129D Isolation
    考虑dp,令\(f_i\)为\([1,i]\)这个前缀的分段方案数。\(i\)从小到大扫描线,动态维护\(c_j\)表示\([j+1,i]\)中只出现恰好一次的数的个数:\[f_i=\sum\limits_{c_j\lek}f_j\]考虑如何维护\(c_j\),扫描线过程中维护\(l_i\)表示\(a_i\)上次出现的位置。那么\(i-1\toi\)......
  • 信息系统项目管理师教程(第四版) 第一章 信息化发展 学习笔记1-20230911
    第一章《信息化发展》 学习要点:1、信息的基本概念、信息的7个质量属性。2、信息系统的概念、特点或用途、抽象模型、信息系统生命周期。3、信息化、信息化系统。4、工业互联网(四大层级)、车联网(体系框架、链接方式、应用场景)。5、农业农村现代化、乡村振兴战略、两化融合与......
  • 【题解】DP选练(23.9.11 - 23.9.12)
    一些写过题解的题我就直接挂连接了。[NOIP2018提高组]货币系统题目描述:在网友的国度中共有\(n\)种不同面额的货币,第\(i\)种货币的面额为\(a[i]\),你可以假设每一种货币都有无穷多张。为了方便,我们把货币种数为\(n\)、面额数组为\(a[1..n]\)的货币系统记作\((n,a)\)。......
  • C++系列三:Qt-for-Python
    目录代码参考:代码参考:官方文档、博客参考代码参考:self.ui.pushButton.setText("demo")lable=QLabel("<fontcolor=redsize=40>HelloWorld!</font>")lable.show()SignalsandSlots:fromPySide6.QtCoreimportSlot@Slot()defsay_hello():......
  • win11查看系统日志详细流程(附图片)
    详细流程如下所示:``打开控制面板。搜索时间查看器(EventView),点击进入查看事件日志。进入事件查看器,选择系统日志。日志界面信息如下图,包括系统全部运行日志基本信息以及详细信息。如需筛选日志类型,可点击筛选当前日志(filterlogs)。输入任务ID或者类型等信息进行......
  • C++系列三:QT-事件处理
    目录介绍:介绍:GUI应用程序是由事件(event)驱动的,点击鼠标,按下按键,窗口大小改变等等按事件的来源,可以将事件划分为3类:自生事件(spontaneousevent):由窗口系统产生,如:QKeyEvent、QMouseEvent。自生事件会进入系统队列,等待事件循环的处理。发布事件(postedevent):是由Qt应用程序产生,如:Q......
  • Sol.CF811B
    题意给定长度为\(n\)的排列,每次选一段区间\([l,r]\)排序,问位置\(x\)上的数在排序前后是否发生了改变。保证\(x\in[l,r]\),共\(q\)次询问。思路可以暴力枚举区间\([l,r]\)内比\(a_x\)小的数,每找到一个\(cnt\)累加一次,最后根据\(l+cnt\)是否等于\(x\)输出代......