Unexpected disconnect of QObject connections
-
Hi all,
we're still using Qt 5.15.18 and noticed a strange behaviour of QObject signal/slot connections. It seems sometimes connections are disconnected and will never be connected again. The question is how to find the cause of this. Here's a simple example:
// main.h #pragma once #include <QDebug> #include <QObject> class A : public QObject { Q_OBJECT public: ~A() { qDebug() << Q_FUNC_INFO; } Q_SIGNAL void valueChanged(QVariant v); }; class B : public QObject { Q_OBJECT public: const A &a; B(const A &a) : a{a} { connect(&a, &A::valueChanged, this, &B::valueChanged); } ~B() { qDebug() << Q_FUNC_INFO; } Q_SIGNAL void valueChanged(QVariant v); }; class C : public QObject { Q_OBJECT const B &b; public: C(const B &b) : b{b} { connect(&b, &B::valueChanged, this, [this](const QVariant &v) { qDebug() << sender()->metaObject()->className() << v; }); connect(&b.a, &A::valueChanged, this, [this](const QVariant &v) { qDebug() << sender()->metaObject()->className() << v; }); } ~C() { qDebug() << Q_FUNC_INFO; } };// main.cpp #include "main.h" int main(int argc, char *argv[]) { A a; B b{a}; C c{b}; emit a.valueChanged(1); emit a.valueChanged({}); emit a.valueChanged(2); return 0; }The output of this example is:
B QVariant(int, 1) A QVariant(int, 1) B QVariant(Invalid) A QVariant(Invalid) B QVariant(int, 2) A QVariant(int, 2) virtual C::~C() virtual B::~B() virtual A::~A()But the output of our App is basically this:
B QVariant(int, 1) A QVariant(int, 1) A QVariant(Invalid) A QVariant(int, 2) virtual C::~C() virtual B::~B() virtual A::~A()As one can see: The outputs of the connection from B are missing after we call a.valueChanged with an empty QVariant. Neither b or a get deleted in between the function calls. And weirdly the issue can be fixed by removing the context object (3rd parameter of connect) or changing it to be the same as the sender:
connect(&b, &B::valueChanged, &b, [this](const QVariant &v) { qDebug() << sender()->metaObject()->className() << v; });Our App is much more complex but this is basically the involved code. Unfortunately the issue our App has doesn't happen in this example. Thus my question is just, how to find out why the App behaves this way.
-
Hi all,
we're still using Qt 5.15.18 and noticed a strange behaviour of QObject signal/slot connections. It seems sometimes connections are disconnected and will never be connected again. The question is how to find the cause of this. Here's a simple example:
// main.h #pragma once #include <QDebug> #include <QObject> class A : public QObject { Q_OBJECT public: ~A() { qDebug() << Q_FUNC_INFO; } Q_SIGNAL void valueChanged(QVariant v); }; class B : public QObject { Q_OBJECT public: const A &a; B(const A &a) : a{a} { connect(&a, &A::valueChanged, this, &B::valueChanged); } ~B() { qDebug() << Q_FUNC_INFO; } Q_SIGNAL void valueChanged(QVariant v); }; class C : public QObject { Q_OBJECT const B &b; public: C(const B &b) : b{b} { connect(&b, &B::valueChanged, this, [this](const QVariant &v) { qDebug() << sender()->metaObject()->className() << v; }); connect(&b.a, &A::valueChanged, this, [this](const QVariant &v) { qDebug() << sender()->metaObject()->className() << v; }); } ~C() { qDebug() << Q_FUNC_INFO; } };// main.cpp #include "main.h" int main(int argc, char *argv[]) { A a; B b{a}; C c{b}; emit a.valueChanged(1); emit a.valueChanged({}); emit a.valueChanged(2); return 0; }The output of this example is:
B QVariant(int, 1) A QVariant(int, 1) B QVariant(Invalid) A QVariant(Invalid) B QVariant(int, 2) A QVariant(int, 2) virtual C::~C() virtual B::~B() virtual A::~A()But the output of our App is basically this:
B QVariant(int, 1) A QVariant(int, 1) A QVariant(Invalid) A QVariant(int, 2) virtual C::~C() virtual B::~B() virtual A::~A()As one can see: The outputs of the connection from B are missing after we call a.valueChanged with an empty QVariant. Neither b or a get deleted in between the function calls. And weirdly the issue can be fixed by removing the context object (3rd parameter of connect) or changing it to be the same as the sender:
connect(&b, &B::valueChanged, &b, [this](const QVariant &v) { qDebug() << sender()->metaObject()->className() << v; });Our App is much more complex but this is basically the involved code. Unfortunately the issue our App has doesn't happen in this example. Thus my question is just, how to find out why the App behaves this way.
@DuBu said in Unexpected disconnect of QObject connections:
Thus my question is just, how to find out why the App behaves this way
Check the lifetime of the objects first.
Check whether you really connect the object you think you do.
Check whether you really connected the signals/slots.
Check the output of the application to see whether there are warnings stating that connection didn't work (only if you use old style connect).
Reduce your app to a minimal reproducible program and post it here. -
@DuBu said in Unexpected disconnect of QObject connections:
Thus my question is just, how to find out why the App behaves this way
Check the lifetime of the objects first.
Check whether you really connect the object you think you do.
Check whether you really connected the signals/slots.
Check the output of the application to see whether there are warnings stating that connection didn't work (only if you use old style connect).
Reduce your app to a minimal reproducible program and post it here.@jsulm Thanks for your reply!
Check the lifetime of the objects first.
I think they are alive, at least I don't see any destructor being called.
Check whether you really connect the object you think you do.
Yes, I do.
Check whether you really connected the signals/slots.
Check the output of the application to see whether there are warnings stating that connection didn't work (only if you use old style connect).Yes, they work unless I call valueChanged with an empty QVariant.
Reduce your app to a minimal reproducible program and post it here.
I would like to, but that's nearly impossible.
-
@jsulm Thanks for your reply!
Check the lifetime of the objects first.
I think they are alive, at least I don't see any destructor being called.
Check whether you really connect the object you think you do.
Yes, I do.
Check whether you really connected the signals/slots.
Check the output of the application to see whether there are warnings stating that connection didn't work (only if you use old style connect).Yes, they work unless I call valueChanged with an empty QVariant.
Reduce your app to a minimal reproducible program and post it here.
I would like to, but that's nearly impossible.
@DuBu said in Unexpected disconnect of QObject connections:
I think they are alive, at least I don't see any destructor being called.
Use
QObject::destroyed()signal while debugging to detect this kind of thing.And weirdly the issue can be fixed by removing the context object (3rd parameter of connect) or changing it to be the same as the sender:
Implies that context object, for the slot, is being destroyed. Removing it or making it same as signalling object prevents that destruction (not saying that's what you want, just saying that would be difference in behaviour).
-
@DuBu said in Unexpected disconnect of QObject connections:
I think they are alive, at least I don't see any destructor being called.
Use
QObject::destroyed()signal while debugging to detect this kind of thing.And weirdly the issue can be fixed by removing the context object (3rd parameter of connect) or changing it to be the same as the sender:
Implies that context object, for the slot, is being destroyed. Removing it or making it same as signalling object prevents that destruction (not saying that's what you want, just saying that would be difference in behaviour).
-
Use QObject::destroyed() signal while debugging to detect this kind of thing.
You say the QObject::destroyed() signal can be emitted, without the destructor being called? I'll give it a try.
@DuBu
I am saying it is worth examining.Since (if I understand right) your test case works but whatever in your more complex app does not, and your app only misbehaves after setting a value of
{}, I would look for any "side effects" of that your app may have. Again if I understand right, it would be explicable if after the set to{}the particular slot object got destroyed. That would leave it with no connection to ever change value again. If you have other objects of same class they might still be, or get, connected, leading to various output. Perhaps verify instances by printing out their address, or assign every object a unique name viaQObject::setObjectName(). -
The problem is using the EOL Qt 5.15.18.
PMF based connections used to silently fail under certain conditions (I know ambiguity was one reason).
They have been significantly improved in Qt 6.
In Qt 6.11 I get this output right away:B QVariant(int, 1) A QVariant(int, 1) B QVariant(Invalid) A QVariant(Invalid) B QVariant(int, 2) A QVariant(int, 2) virtual C::~C() virtual B::~B() virtual A::~A()If you wanna check which connection fails, you can output the return value of the connect statement e.g. like that:
B(const A &a) : a{a} { const auto connection = connect(&a, &A::valueChanged, this, &B::valueChanged); qInfo() << connection; }(The return value is of the type
QMetaObject::Connection, but it's debug operator just treats it like a bool.)