美文网首页
Android 一步步讲解LightRefBase 实现原理

Android 一步步讲解LightRefBase 实现原理

作者: 付凯强 | 来源:发表于2022-08-07 21:12 被阅读0次

LightRefBase 是为了避免指针变量的内存泄漏而提出的解决方案。这篇文章循序渐进地用示例说明 LightRefBase 是如何解决指针变量的内存泄漏的。

指针的内存泄漏

Student.h

#ifndef CPLUS_STUDENT_H
#define CPLUS_STUDENT_H
#include <iostream>
using namespace std;
class Student {
public:
    Student();
    ~Student();
    void play();
};
#endif //CPLUS_STUDENT_H

Student.cpp

#include "Student.h"
Student::Student() {
    cout << "构造方法" << endl;
}
Student::~Student() {
    cout << "析构函数" << endl;
}
void Student::play() {
    cout << "Student play" << endl;
}
int main() {
    Student student;
    student.play();
    return 0;
}
构造方法
Student play
析构函数

当在栈里面创建对象的时候,会自动执行析构函数,但是在堆里创建对象的时候,并不会自动执行执行析构函数。这个时候就会发生内存泄露。

#include <iostream>
#include "Student.h"

int main() {
    auto student = new Student;
    student->play();
    return 0;
}
构造方法
Student play

解决办法

既然堆里对象的指针自己无法管理自己的生命周期,那就让其他来管理它的生命周期。这个其他指的是一个类,通过这个类给对象的指针赋值,通过这个类的析构函数来释放这个指针,这个类的创建选择在栈里创建,当操作完成后会自动执行析构函数,在执行析构函数的时候执行释放堆里对象的指针的操作。
这个类,这里取名为sp。
sp.h

#ifndef CPLUS_SP_H
#define CPLUS_SP_H
#include "Student.h"
using namespace std;
class sp {
    Student* student;
public:
    sp();
    sp(Student* another);
    ~sp();
    Student* operator->();
};
#endif //CPLUS_SP_H

sp.cpp

#include "sp.h"

sp::sp() {
    cout << "sp 无参构造" << endl;
}
sp::sp(Student *another) {
    cout << "sp 有参数构造" << endl;
    student = another;
}
sp::~sp() {
    cout << "sp 析构函数()" << endl;
    if (!student) return;
    delete student;
}
Student* sp::operator->() {
    return student;
}
#include <iostream>
#include "Student.h"
#include "sp.h"

int main() {
    sp p = new Student;
    p->play();
    return 0;
}
Student 构造方法
sp 有参数构造
Student play
sp 析构函数()
Student 析构函数

通过将Student对象的指针放在sp类中,通过sp类进行赋值和执行析构函数并释放指针,我们达到了让sp管理对象的指针的目的。

重复析构问题

void test(sp& another){
    sp s = another;
    s->play();
}
int main() {
    sp p = new Student;
    for (int i =0;i < 2;i++){
        test(p);
    }
    return 0;
}
/home/fukaiqiang/Cplus/cmake-build-debug/Cplus
free(): double free detected in tcache 2
Student 构造方法 0x5633570efeb0
0x7ffc0fbe79b0 sp 有参数构造
Student play 0x5633570efeb0
0x7ffc0fbe7960 sp 析构函数()
student = 0x5633570efeb0 delete student
Student 析构函数 0x5633570efeb0
Student play 0x5633570efeb0
0x7ffc0fbe7960 sp 析构函数()
student = 0x5633570efeb0 delete student
Student 析构函数 0x5633570efeb0

Process finished with exit code 134 (interrupted by signal 6: SIGABRT)

main.cpp中添加了test方法,test方法的参数类型是sp的引用。在main函数中开启一个for循环,执行两次test方法,每次执行完test方法后就会调用sp的析构函数,但由于Student*所指向的内存空间已经在for循环的第一个循环中被释放过一次了,所以这里会报重复释放的报错。

引用计数

为了解决重复析构的问题,选择为每个对象增加计数,只要一个指针指向这个对象,就会执行加1的操作。这个计数由对象自身自己掌握,后面的文章会讲解为什么。为此创建RefBase类类管理计数。
RefBase.h

#ifndef CPLUS_REFBASE_H
#define CPLUS_REFBASE_H
class RefBase {
    int count;
public:
    RefBase(): count(0){};
    void incStrong();
    void decStrong();
    int getStrongCount();
};
#endif //CPLUS_REFBASE_H

RefBase.cpp

#include "RefBase.h"

void RefBase::incStrong() {
    count++;
}
void RefBase::decStrong() {
    count--;
}
int RefBase::getStrongCount() {
    return count;
}

Student.h

#include "Student.h"

Student::Student() {
    cout << "Student 构造方法 " << this << endl;
}
Student::~Student() {
    cout << "Student 析构函数 " << this << endl;
}
void Student::play() {
    cout << "Student play " << this << endl;
}

sp.cpp

sp::sp(Student *another) {
    cout << this << " sp 有参数构造" << endl;
    student = another;
    student->incStrong();
}
sp::~sp() {
    cout << this <<  " sp 析构函数()"  << endl;
    if (student) {
        student->decStrong();
        if (student->getStrongCount() == 0) {
            cout << "student = " << student << " delete student" << endl;
            delete student;
        }
    }
}
Student 构造方法 0x55ce6ef15eb0
0x7ffcf05e3c80 sp 有参数构造
Student play 0x55ce6ef15eb0
0x7ffcf05e3c30 sp 析构函数()
student = 0x55ce6ef15eb0 delete student
Student 析构函数 0x55ce6ef15eb0
Student play 0x55ce6ef15eb0
0x7ffcf05e3c30 sp 析构函数()
0x7ffcf05e3c80 sp 析构函数()

引入类模板

用模板类替换Student类,模板类放在头文件中。

#ifndef CPLUS_SP_H
#define CPLUS_SP_H
#include "Student.h"
using namespace std;
template<typename T>
class sp {
    T* t;
public:
    sp();
    sp(T* another);
    ~sp();
    T* operator->();
};
#endif //CPLUS_SP_H

template<typename T>
sp<T>::sp() {
    cout << " sp 无参构造" << endl;
}
template<typename T>
sp<T>::sp(T *another) {
    cout << this << " sp 有参数构造" << endl;
    t = another;
    t->incStrong();
}
template<typename T>
sp<T>::~sp() {
    cout << this <<  " sp 析构函数()"  << endl;
    if (t) {
        t->decStrong();
        if (t->getStrongCount() == 0) {
            cout << "t = " << t << " delete t" << endl;
            delete t;
        }
    }
}
template<typename T>
T* sp<T>::operator->() {
    return t;
}

修改sp.h,去掉sp.cpp,修改main.cpp.

#include <iostream>
#include "Student.h"
#include "sp.h"
template<typename T>
void test(sp<T>& another){
    sp<T> s = another;
    s->play();
}

int main() {
    sp<Student> p = new Student();
    for (int i =0;i < 2;i++){
        test(p);
    }
    return 0;
}

同步计数器

count 并非原子的,在多线程情况下会有问题。这个时候我们借助LightRefBase.h类。

  1. 删除自己写的sp.h和sp.cpp以及Refbase.h和Refbase.cpp
  2. 将 frameworks/rs/cpp/util 中的文件copy到项目中,分别是RefBase.h、StrongPointer.h和TypeHelpers.h
  3. 修改Student.h以及main.cpp
#ifndef CPLUS_STUDENT_H
#define CPLUS_STUDENT_H
#include <iostream>
#include "RefBase.h"
#include "LightRefBase.h"

using namespace std;
class Student : public LightRefBase<Student>{
public:
    Student();
    ~Student();
    void play();
};
#include "Student.h"
#include "RefBase.h"
using namespace std;
using namespace android::RSC;

template<typename T>
void test(sp<T>& another){
    another->play();
}

int main() {
    sp<Student> p = new Student();
    for (int i =0;i < 2;i++){
        test(p);
    }
    return 0;
}

总结

如果想使用智能指针,只需要做两步。

  1. 让核心类继承LightRefBase。
  2. 创建类对象需要使用sp类。

参考

https://blog.csdn.net/musicalspace/article/details/106223484

相关文章

网友评论

      本文标题:Android 一步步讲解LightRefBase 实现原理

      本文链接:https://www.haomeiwen.com/subject/lwgvbrtx.html