适合具备 C 语言基础的 C++ 教程(十五)

2021-03-22 15:15:14 浏览数 (1)

前言

在上一则教程中,叙述了当处于多线程环境下时,智能指针所指向的引用计数可能会因为此导致引用计数出问题,因此,引入了原子操作的相关概念,换句话说,这种操作也被称之为是轻量级指针,那对于这种轻量型指针又会存在什么问题呢?本节内容将着重叙述这个问题。另外需要注意的是,关于最近几次的内容互相之间都是息息相关的,需要结合上下文进行理解,同时,因为涉及到的代码比较多,如果哪里没有说明白的地方,需要下载对应的源代码进行对照分析。好了,接下来,进入本次内容的分享。

强指针

在说明强指针这个概念之前,我们先从代码的角度慢慢分析,首先,假设,我们现在有如下两个智能指针:

image-20210313102009432

如上图所示,A 指针指向了 B,B 指针指向了 A,这样会导致什么后果呢,我们看如下所示的代码,在上一节轻量级指针的基础上,我们构建这样的 Person类代码:

代码语言:javascript复制
class Person : public LightRefBase<Person>
{
private:
    sp<Person> father;
    sp<Person> son;

public:
    Person()
    {
        cout << "Person()" << endl;
    }

    ~Person()
    {
        cout << "~Person()" << endl;
    }

    void setFather(sp<Person> &father)
    {
        this->father = father;
    }

    void setSon(sp<Person> &son)
    {
        this->son = son;
    }

    void printInfo(void)
    {
        cout << "just a test function" << endl;
    }
};

基于此,我们编写一个测试函数,代码如下所示:

代码语言:javascript复制
void test_func(void)
{
    sp<Person> father = new Person();
    sp<Person> son = new Person();

    father->setSon(son);
    son->setFather(father);
}

然后,我们是主函数,代码如下所示:

代码语言:javascript复制
int main(int argc, char **argv)
{
    test_func();

    return 0;
}

在分析,强指针之前,我们先来分析以下上述过程中构造函数和析构函数的一个过程,对于类里面的各个对象成员以及类本身而言,它的构造顺序基于这样一个原理:如果对象里面含有其他对象成员,那么在构造的时候:先构造其他对象成员,再构造对象本身;而析构时:顺序则刚刚相反。基于这样一个原理,我们来分析如下所示的代码:

代码语言:javascript复制
sp<Person> father = new Person();

对于 new Person()来说,Person对象里面的father先被构造,然后紧接着是Person对象里面的son被构造,最后是Person对象本身被构造。对于这句代码,Person对象的指针传给了sp<Person> father,这也就导致了sp(T *other)被调用,而这步操作也就增加了这个Person对象的引用计数(此时引用计数值等于 1)

紧接着,我们来看下面这句代码:

代码语言:javascript复制
sp<Person> son = new Person();

对于这句代码它的原理和上一句是相同的,首先是Person对象里的father先被构造,然后是son被构造,紧接着是Person对象本身被构造。Person对象的指针传给sp<Person> son,导致sp(T *other)被调用,它增加了Person对象的引用计数,现在这个值等于1。

到这里,我们知道,两个 Person对象的引用计数都等于1,我们接着看如下所示的代码:

代码语言:javascript复制
father->setSon(son);

这个函数里面有一个等号赋值操作,其实这个等号是经过重载的,我们来看等号重载的代码:

image-20210313110017132

可以看到,在这个函数里,有对于引用计数自增的操作,也就是说通过father->setSon(son)操作会使得son所指向的引用计数值自增,所以到这里,son所指向的对象的引用计数的值就为2

同样的,我们再来看如下所示的代码:

代码语言:javascript复制
son->setFather(father);

根据上述分析, father所指向的对象的引用计数值变为2,这么一来,当函数test_func调用完成然后退出的时候,会释放相应的局部变量,但是我们之前在叙述智能指针的时候,提到过,要释放智能指针所指向的对象的内存,需要当所指向的对象的引用计数为 0 的时候,才能将其释放掉,所以上述代码就算test_func运行结束,也不会去销毁对象的内存。下图是上述代码的一个示意图:

image-20210313112741305

向上述这种情况,就称之为是强指针,也就是 A 指向 B,A 决定 B 的生死,最后,我们来看,我们最初给出的代码的运行结果:

image-20210313113733367

可以看到虽然test_func执行完毕,但是并没有执行析构函数,要如何解决这个问题呢,就需要引入弱指针的相关概念。

弱指针

引入弱指针的原因在上述已经说明了,那么具体应该如何做呢?对比于上述的强指针,我们可以引入一个弱指针的计数,同时增加一些弱指针计数的相关操作,下面是简化的代码:

代码语言:javascript复制
class RefBase
{
private:
    int mStrong;
    int mWeak;

public:
    void incStrong(void);
    void decStrong(void);

    void incWeak(void);
    void decWeak(void);
};

写到这里我们其实又可以将弱指针再抽象为一个对象,在这里,(incStrong不放入是因为为了兼容之前轻量级指针对引用计数的操作),如下面的代码所示:

代码语言:javascript复制
class StrongWeakRef_type 
{
private:
    int mstrong;
    int mweak;
public:
    void incWeak();
    void decWeak();
};

我们在接触面向对象的编程中,可以知道一个原则,就是说,在头文件中是不想看到私有数据成员的,只想看到接口,那么我们在实现这部分的时候,就可以将其进一步优化,也就是拆分成两部分:

  • 固定接口类
  • 变化的实现

抽象得到的接口类的代码如下所示:

代码语言:javascript复制
class weakref_type
{
public:
    void incWeak(void);
    void decWeak(void);
};

将这部分代码放入头文件中,接下来是变化的实现的代码,这部分代码是放在 cpp文件中的:

代码语言:javascript复制
class weakref_impl:public weakref_type
{
private:
    int mstrong;
    int mWeak;
};

基于上述的代码,再来写出我们的 RefBase类的代码:

代码语言:javascript复制
class RefBase
{
    weakref_impl * mRefs;
};

这样,我们就完成了一个强弱指针统一实现的一个基本思路,下图是关于上述的一个分析过程的一个示意图:

image-20210313145313229

上述仅仅是一个思路,具体的代码比这要复杂,就不进行剖析了,我们来看基于刚刚所述的这个弱指针,我们继续来实现我们的 Person类。

代码语言:javascript复制
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <utils/RefBase.h>

using namespace std;
using namespace android;

class Person : public RefBase {

private:
    wp<Person> father;
    wp<Person> son;

public:
    Person() {
        cout <<"Pserson()"<<endl;
    }


    ~Person()
    {
        cout << "~Person()"<<endl;
    }

    void setFather(sp<Person> &father)
    {
        this->father = father;
    }

    void setSon(sp<Person> &son)
    {
        this->son = son;
    }

    void printInfo(void)
    {
        cout<<"just a test function"<<endl;
    }
};

上述代码中引入的是Android源代码的相关头文件,在这基础上实现的 Person类,我们可以看到相对于前文所述的 Person类,sp变成了wp,其他代码不变,同样是造成了对 fatherson所指向的对象的两次引用,但是在程序执行的时候,结果如下所示:

image-20210313150255127

这也正是使用弱指针所带来的效果。

小结

这就是本次所要分享的内容,涉及到强指针和弱指针的相关介绍,所涉及的代码和Android源代码相关,如果想要查看源代码的朋友,可以通过下方的百度云链接进行下载。

代码语言:javascript复制
链接:https://pan.baidu.com/s/1NlHqvrdvKI9IBLj_EKnk1g 
提取码:w7nj

0 人点赞