前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >适合具备 C 语言基础的 C++ 教程(十五)

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

作者头像
wenzid
发布2021-03-22 15:15:14
4830
发布2021-03-22 15:15:14
举报
文章被收录于专栏:wenzi嵌入式软件

前言

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

强指针

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

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
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 wenzi嵌入式软件 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 强指针
  • 弱指针
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档