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类。
- 删除自己写的sp.h和sp.cpp以及Refbase.h和Refbase.cpp
- 将 frameworks/rs/cpp/util 中的文件copy到项目中,分别是RefBase.h、StrongPointer.h和TypeHelpers.h
- 修改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;
}
总结
如果想使用智能指针,只需要做两步。
- 让核心类继承LightRefBase。
- 创建类对象需要使用sp类。
参考
https://blog.csdn.net/musicalspace/article/details/106223484
网友评论