Android 中的智能指针是经过引证计数的方法方法来实现内存自动收回的。在大多数情况下咱们运用强指针 sp 就好了,那么弱指针 wp 的存在含义有是什么呢?

从运用的视点来说,wp 扮演的是一个指针缓存的角色,想用时分可以用,但不想因而阻挠资源被开释。其实,简略的裸指针也能很好地完结指针缓存的功能,其功能性并不是 wp 存在的必要条件。

wp 存在的核心原因是:处理循环引证导致的死锁问题

1. 循环引证导致的死锁问题

接下来,咱们就经过一个简略的示例程序来演示循环引证导致的死锁问题

首先有两个类,其内部都有一个智能指针指向对方,构成循环引证:

Class A : public RefBase
{
public:
    A() 
    {
    }
    virtual ~A() 
    {
    }
    void setB(sp<B>& b) 
    { 
        mB = b; 
    }
private:
    sp<B> mB;
}
Class B : public RefBase
{
public:
    B() 
    {
    }
    virtual ~B() 
    {
    }
    void setA(sp<A>& a) 
    { 
        mA = a; 
    }
private:
    sp<A> mA;
}

整体结构如下图所示:

为什么需要弱引用 wp?

接下来看 main 函数:

int main(int argc, char** argv)
{
    //初始化两个指针
    A *a = new A();
    B *b = new B();
    // 触发结构函数调用 spA 内部强弱计数值 (1,1)
    sp<A> spA = a;
    // 触发结构函数调用 spB 内部强弱计数值 (1,1)
    sp<B> spB = b;
    //setB 内部有赋值操作 mB = b,触发等于操作符函数重载
    //spB 内部强弱计数值 (2,2)
    spA->setB(spB);
    //setA 内部有赋值操作 mA = a,触发等于操作符函数重载
    //spA 内部强弱计数值 (2,2)
    spB->setA(spA);
    return 0;
    // spA 析构 内部强弱计数值 (1,1),内存无法收回
    // spB 析构 内部强弱计数值 (1,1),内存无法收回
}
//等于操作符函数重载
template<typename T>
sp<T>& sp<T>::operator =(const sp<T>& other) {
    // Force m_ptr to be read twice, to heuristically check for data races.
    T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
    T* otherPtr(other.m_ptr);
    // 强弱引证计数分别加 1
    if (otherPtr) otherPtr->incStrong(this);
    if (oldPtr) oldPtr->decStrong(this);
    if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
    m_ptr = otherPtr;
    return *this;
}

从这个示例可以看出,在循环引证的情况下,指针指针在效果域结束后,强弱引证计数值无法变回 (0,0),内存无法收回,导致内存走漏;

2. 处理方案

只需要把其间一个智能指针改为弱引证即可处理上面的问题:

Class A : public RefBase
{
public:
    A() 
    {
    }
    virtual ~A() 
    {
    }
    void setB(sp<B>& b) 
    { 
        mB = b; 
    }
private:
    sp<B> mB;
}
Class B : public RefBase
{
public:
    B() 
    {
    }
    virtual ~B() 
    {
    }
    //函数参数也要变一下
    void setA(sp<A>& a) 
    {   
        //触发另外的等于操作符函数重载 
        mA = a; 
    }
private:
    //这儿改成 wp 弱引证
    wp<A> mA;
}

主函数稍作修正:

int main(int argc, char** argv)
{
    //初始化两个指针
    A *a = new A();
    B *b = new B();
    // 触发结构函数调用  spA 内部强弱计数值 (1,1)
    sp<A> spA = a;
    // 触发结构函数调用  spB 内部强弱计数值 (1,1)
    sp<B> spB = b;
    //setB 内部有赋值操作 mB = b,触发等于操作符函数重载
    //spB 内部强弱计数值 (2,2)
    spA->setB(spB);
    //setA 内部有赋值操作 mA = a,触发等于操作符函数重载
    //spA 内部强弱计数值 (1,2)
    spB->setA(spA);
    return 0;
    // spB 析构 内部强弱计数值 (1,1),内存无法收回
    // spA 析构 内部强弱计数值 (0,1),强引证为 0 ,收回 sp<A> spA 内部的方针方针 A,
    // 随着 A 的析构, A 的成员变量 mB 也开端析构, 方针方针 B 强弱引证计数减 1,内部强弱计数值变为 (0,0),收回方针方针 B 以及内部办理方针,B 方针的内存收回作业完结,接着触发 B 方针的成员 mA 的析构函数
    // mA 履行析构函数,弱引证计数减 1,内部强弱计数值变为 (0,0),收回 A 方针内部对应的办理方针,A 方针的内存收回作业完结
}
//等于操作符函数重载
template<typename T>
wp<T>& wp<T>::operator = (const sp<T>& other)
{
    weakref_type* newRefs =
        other != nullptr ? other->createWeak(this) : nullptr; //增加弱引证计数
    T* otherPtr(other.m_ptr);
    if (m_ptr) m_refs->decWeak(this);
    m_ptr = otherPtr;
    m_refs = newRefs;
    return *this;
}

当程序的一个引证修正为 wp 时,main 函数结束时:

  • spB 析构 内部强弱计数值 (1,1),内存无法收回
  • spA 析构 内部强弱计数值 (0,1),强引证为 0 ,收回 sp<A> spA 内部的方针方针 A,
  • 随着 A 的析构, A 的成员变量 mB 也开端析构, 方针方针 B 强弱引证计数减 1,内部强弱计数值变为 (0,0),收回方针方针 B 以及内部办理方针,B 方针的内存收回作业完结,接着触发 B 方针的成员 mA 的析构函数
  • mA 履行析构函数,弱引证计数减 1,内部强弱计数值变为 (0,0),收回 A 方针内部对应的办理方针,A 方针的内存收回作业完结

这样就处理了上一节中提出的内存走漏问题!

3. 总结

  • wp 的基本效果:wp 扮演了指针缓存的角色,想用时分可以用,但不想因而阻挠资源被开释
  • wp 存在的根本原因:处理循环引证导致的死锁问题

关于

我叫阿豪,2015 年本科结业于国防科学技术大学指挥信息系统专业,结业后从事信息化装备的研制作业,主要研究方向是 Android Framework 与 Linux Kernel。

假如你对 Android Framework 感兴趣或许正在学习 Android Framework,可以重视我的微信大众号和抖音,我会继续共享我的学习经历,帮助正在学习的你少走一些弯路。学习过程中假如你有疑问或许你的经历想要共享给我们可以增加我的微信,我拉你进技术交流群。

为什么需要弱引用 wp?