背景
今天现场反馈了一个软件崩溃的bug,这个bug造成的异常居然没被捕获生成dump文件,很奇怪。但是种种努力复现了这个bug。简单记录一下。
实验
假设有这么两个类
ClassA
ClassA.h
#ifndef CLASSA_H
#define CLASSA_H
#include <QObject>
class ClassA : public QObject
{
Q_OBJECT
public:
explicit ClassA(QObject *parent = nullptr);
signals:
void aboutToRemove();
};
#endif // CLASSA_H
ClassA.cpp
#include "classa.h"
ClassA::ClassA(QObject *parent) : QObject(parent)
{
}
ClassB
ClassB.h
#ifndef CLASSB_H
#define CLASSB_H
#include "classa.h"
class ClassB : public QObject
{
Q_OBJECT
public:
explicit ClassB(QObject *parent = nullptr);
~ClassB();
void setClassA(ClassA* a);
signals:
private:
ClassA *mA;
};
#endif // CLASSB_H
ClassB.cpp
#include "classb.h"
#include "classa.h"
#include <QDebug>
#include <QDateTime>
ClassB::ClassB(QObject *parent) : QObject(parent)
{
mA = nullptr;
}
ClassB::~ClassB()
{
qDebug() << "ClassB::~ClassB()";
}
void ClassB::setClassA(ClassA *a)
{
mA = a;
connect(mA, &ClassA::aboutToRemove, [&](){
// connect(mA, &ClassA::aboutToRemove, this, [&](){
qDebug() << QDateTime::currentDateTime() << "about to remove";
mA->disconnect(this);
mA = nullptr;
});
}
然后在函数中调用
main.cpp
int main(int argc, char *argv[])
{
...
ClassA *clsA = new ClassA();
ClassB *clsB = new ClassB();
clsB->setClassA(clsA);
delete clsB;
clsB = nullptr;
clsA->aboutToRemove();
...
}
现象
1.直接运行上面的代码
输出
ClassB::~ClassB()
QDateTime(2022-10-14 22:00:58.039 中国标准时间 Qt::LocalTime) about to remove
2.修改后再运行
此时输出:
ClassB::~ClassB()
分析
lambda函数作为槽函数时,有两种写法
[static] QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
[static] QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)
主要区别在于第二个写法指定了一个context,这个context和sender联合保证了这条connection的有效期。
因此,在前面的实验中,第一次用的lambda槽函数是不带context,即使delete了clsB,这个connection依然还是存在,所以信号触发时,lambda函数会被继续执行。
第二次用的是带context的,contex(clsB)被析构时,这个connection也就被断开了,lambda函数就不会被执行了。
结论
在使用lambda作为槽函数时,最好加上个context,否则就要百分百确保信号发生时,lambda函数内的变量都是可用的。