什么是智能指针

  • 正如你我都时刻担心的一件事情,我们的编程过程中,免不了要使用指针指向一块空间,那么就可能因为粗心大意而造成“内存泄露”的问题
  • 当然,你肯定会呓呓自语:“我肯定没那么蠢”,然后默默地一直仔细的把关防止出现内存管理错误,但是忽然有一天,错误依旧发生了,好懊恼
  • 那么与其自己一直的关系,可不可以有一种托管思路的工具诞生呢,有,就是“智能指针”
  • 智能指针,是一种利用RAII思想的设计类,提供类似指针的用途,通过类对象的构造与析构,对自己管理的资源进行合理的释放与维护(依托于这个类对象的生命周期)

    RAII思想

    RAII,全称“Resource Acquisition Is Initialization”,是一种利用对象的生命周期来进行控制程序资源(如 内存, 句柄, 网络连接, 互斥量等等)的简单技术

    智能指针的好处

    在对象构造的时候获取资源,在对象生命周期内对 所控制资源的访问 保持有效,最后在对象析构时释放所保持的资源
    因此,如果使用智能指针,我们可以不再需要显示的释放资源,这会带来很大的便捷

    智能指针的实现

    简单介绍智能指针的发展

    目前常见的智能指针三种

智能指针 auto_ptr unique_ptr shared_ptr
优点 初步实现了指针的功能 初步实现了指针的功能,避免了auto_ptr拷贝与赋值的问题 较为成熟的一种智能指针,采用了“引用计数”的方法,根本上解决前面俩个拷贝与赋值的问题
缺点 原理是管理权转移,在拷贝与赋值行为中,造成赋值者失效 由于实现的原理,无法满足拷贝构造和赋值 计数回绕的问题

模拟实现auto_ptr

“`c++
template<class T> // 模板
class Auto_ptr
{
// 基本不被使用,存在指针转移的问题
public:
//构造
Auto_ptr(T ptr = nullptr)
:m_ptr(ptr)
{}
//析构
~Auto_ptr()
{
if (m_ptr) delete m_ptr;
}
// 解引用 返回
ptr
T& operator() { return m_ptr; } //为什么拿引用来接受呢

// 指向 返回指针
T* operator->() { return m_ptr; }
// 指针的拷贝构造
Auto_ptr(Auto_ptr<T>& sp)
    :m_ptr(sp.m_ptr)
{
    sp.m_ptr = nullptr;
}
//赋值运算符
Auto_ptr<T>& operator=(Auto_ptr<T>& sp)  //参数为引用类型, 返回为引用类型,为什么
{
    //首先判断是否为本身
    if (sp == *this) return;
    //清空现有资源
    if (m_ptr) delete m_ptr;
    //转移指针指向,避免多指向引发野指针问题,sp.m_ptr = nulptr;
    m_ptr = sp.m_ptr;
    sp.m_ptr = nullptr;
}

private:
T* m_ptr;
};

### 模拟实现unique_ptr
```c++
template<class T>
class Unique_ptr
{
    //避免转移,那就意味着不需要去考虑拷贝和赋值
public:
    //构造
    Unique_ptr(T* ptr = nullptr)
        :m_ptr(ptr)
    {}
    //析构
    ~Unique_ptr()
    {
        if (m_ptr) delete m_ptr;
    }
    //*
    T& operator*() { return *m_ptr; }
    //->
    T* operator->() { return m_ptr; }
private:
    Unique_ptr(Unique_ptr<T>& Up) = delete; // or only decarler but not make it;
    Unique_ptr<T>& operator=(Unique_ptr<T>& Up) = delete;

private:
    T* m_ptr;
};

模拟实现shared_ptr

说到shared_ptr,一定需要考虑到,在它的实现原理中,因为存在“引用计数”,所以为了基本的线程安全,必须对“计数过程的++ –保证线程安全”,同时也保证析构时“计数为0采去彻底释放”

  • “`c++
    template<class T>
    class Shared_ptr
    {
    public:
    //构造
    Shared_ptr(T ptr = nullptr)
    :m_ptr(ptr)
    , pRefCount(new int(1)) // 构造本身默认count 为1;
    , pMutex(new mutex)
    {}
    //析构 由于析构比较复杂,可以内置一个私有函数Release()进行析构
    ~Shared_ptr()
    {
    Release();
    }
    //

    T& operator() { return m_ptr; }
    //->
    T* operator->() {
    return m_ptr;
    }

    //线程安全的计数+1 AddRefCount 私有
    //拷贝
    Shared_ptr(Shared_ptr<T>& Sp)
    :m_ptr(Sp.m_ptr)
    ,pRefCount(Sp.pRefCount)
    , pMutex(Sp.pMutex)
    {
    AddRefCount();
    }
    //赋值
    Shared_ptr<T> operator=(Shared_ptr<T>& sp)
    {
    if (sp != *this)
    {
    //情况自己本身的属性
    Release();
    //赋值原属性, 再对资源计数+1
    m_ptr = sp.m_ptr;
    pRefCount = sp.pRefCount;
    pMutex = sp.pMutex;

        AddRefCount();
    }

    }

private:
//+ – 计数都必须考虑线程安全问题
void Release()
{
//when recountf == 0, delete ref
bool isRefNull = false;
pMutex->lock();
if (–(pRefCount) == 0)
{
delete m_ptr;
delete pRefCount;
//现在当然还不可以删除锁资源,小脑袋瓜想什么着
isRefNull = true;
}
pMutex->unlock();
if (isRefNull) delete pMutex;
}
void AddRefCount()
{
//保证计数线程安全
pMutex->lock();
++(
pRefCount);
pMutex->unlock();
}
private:
T m_ptr;
mutex
pMutex;
int* pRefCount; //为什么作为成员变量呢, 这样应用计数的值就是对象的一个属性,每个对象都独立的拥有一份
};

#### 循环引用的问题,它的破解之刃——weak_ptr
* 当我们把话题引导到“引用计数”的问题上时,就避免不开的讨论“计数回绕”的问题了,简单来说:“计数回绕是一种采用引用计数,因为某种联系造成彼此引用(循环引用),恰好,双方shared_ptr管理资源的释放均依托于对方,造成“锁”状态,从而资源释放不了,造成内存泄露”
```C++
struct ListNode
{
 int _data;
shared_ptr<ListNode> _prev;
 shared_ptr<ListNode> _next;
 ~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
 shared_ptr<ListNode> node1(new ListNode);
 shared_ptr<ListNode> node2(new ListNode);
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 node1->_next = node2;
 node2->_prev = node1;
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 return 0;
 }

段落引用
image.png

解决循环引用问题的weak_ptr

“`C++
struct ListNode
{
int _data;
weak_ptr<ListNode> _prev;// 对比上面就把这里改为weak_ptr即可
weak_ptr<ListNode> _next; // 如上所示, 使用weak_ptr对存在循环引用,回绕的计数不进行+1;
~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0; }