[Development] Not delivering signals to slots in class sub-objects already destroyed

Thiago Macieira thiago.macieira at intel.com
Thu Sep 18 03:05:56 CEST 2025


On Wednesday, 17 September 2025 03:12:38 Pacific Daylight Time Giuseppe 
D'Angelo via Development wrote:
> My take:
> 
> > a) should this functionality be provided at all?
> 
> Yes, but AFAICT it's impossible to provide it in a way which is correct 
> and complete (like the string-based used to be), unless we force our 
> hand in some place.

It can be as complete as what the string-based one used to be, assuming one 
didn't overwrite qt_metacall directly without the help of moc. My issue isn't 
comparing it to the string-based connection, but to the current functionality.

> > b) should it be provided by default for PMFs? [*]
> 
> Yes, but I think we should clarify what PMF actually means here...

std::is_member_function_pointer_v is true.

> I have no idea how to achieve this reliably without RTTI: you can 
> connect to an arbitrary PMF of a QObject subclass that doesn't use 
> Q_OBJECT and therefore whose "distance" from QObject is a wrong metric:
>
> struct A : public QObject
> {
>      Q_OBJECT
>      ~A() { emit aSignal(); }
> signals:
>      void aSignal();
> };
> 
> struct B : public A
> {
>      B() { connect(this, &B::aSignal, this, &B::aSlot); }
>      void aSlot();
> };
> 
> ~A() will still call the slot, won't it? But it mustn't.

Indeed, but this is not something that the string-based connection supported, 
because you couldn't have slots without Q_OBJECT.

You're right that there's no way to implement this without RTTI. And 
std::type_info doesn't have the functionality we want in the standard API. 
It's there in the private ABI, but not something we can portably access.

Therefore, detecting B's destruction is impossible. So the above won't 
disconnect with the new functionality, resulting in it staying as status quo 
from today. I don't think that's a problem.

> To complicate things further, the type of the receiver isn't the one 
> that actually matters:
> 
> 
> struct A : public QObject
> {
>      Q_OBJECT
>      ~A() { emit aSignal(); }
>      void aSlot();
> signals:
>      void aSignal();
> };
> 
> struct B : public A
> {
>      B() { connect(this, &B::aSignal, this, &B::aSlot); }
> };
> 
> Now A::aSlot() must be called instead.

This one isn't a problem, because &B::aSlot is actually of type 
	void (A:: *)()

The dynamic type of this at this point is not important. What matters is the 
class type embedded in the PMF.

> We could bring back the "rule" that you should only connect to PMFs of 
> classes that do have Q_OBJECT, or at least enforce it as best practice, 
> say with some Clazy warnings (which already warns for QObject subclasses 
> without Q_OBJECT in general). With this rule in place, would we be able 
> to go back to the behavior of string-based connections?

It's possible to statically assert it too. See the helper class 
QtPrivate::HasQ_OBJECT_Macro used in qobject_cast. But I don't think it's 
something people will opt-in, so a clazy warning might be best.

> B) if you're connecting to something else than a PMF, and you're passing 
> a context object, then the check applies to the context. Not passing a 
> context object is _definitely_ bad practice because of lifetime and 
> threading issues; right now there's an opt-in 
> (QT_NO_CONTEXTLESS_CONNECT, also set by QT_ENABLE_STRICT_MODE_UP_TO) but 
> we should consider raising this to a deprecation level.

Experience with the change is showing a lot of code depends on delivering even 
when the context object has begun destruction and is no longer of the type 
that it was during connection.

> > e) if there's a flag to opt in or out, what do we call the
> > Qt::XxxConnection?
> 
> Is there a valid use-case for even wanting an opt-out mechanism? 
> String-based connects don't have an opt-out one.

This was about the non-PMF case. There are plenty of examples in our code, 
such as all connections to &QObject::destroyed where sender == receiver, like 
that of Q_APPLICATION_STATIC discussed below. Another is in qfuture_impl.h.

The CI is also failing on QRangeModel. I don't know where (haven't debugged) 
but I suspect is another one of these (though I can't find where).

> If we force our hand elsewhere and require RTTI instead, would we able 
> to provide a faster one, by checking the type_infos of the associated 
> objects? (This would require using non-portable code, surely, but it's 
> not that such a thing should scare us away.)

The problem is that there is no portable way to check if a given 
std::type_info is a parent class of another std::type_info. The information is 
there in every single RTTI because it's how dynamic_cast is implemented, but I 
don't want to write, test, maintain, and support code for three ABIs (Itanium, 
MSVC, INTEGRITY's).

The only thing we can rely on is QMetaObject.

> > 2) I think it's safe to apply this by default when the receiver is a PMF
> > and the class of the PMF has destroyed (implemented on the commit after
> > the above, 672088), but what about when the receiver is a static or
> > non-member function, or lambda or functor? The lifetime of the receiver's
> > current dynamic class is not tightly bound with the callback. An example
> > of this is
> > Q_APPLICATION_STATIC:
> > 
> > 
> >   QObject::connect(app, &QObject::destroyed, app, reset,
> >   Qt::DirectConnection);
> 
> Here I think we may need a separate connect() overload, because the 3rd 
> argument is pointless since one is calling a static function.

This one above used to be a contextless connection. There are other similar 
cases where the only context is that of the sender, with everything else 
captured in the receiver lambda.

> Why a different overload rather than just using the existing overload of 
> connect()? Because the existing overload has shown that it's terribly 
> error-prone. The hope would be that a connectToStatic() would help avoid 
> such issues.
> 
> (Alternatively, the 3-arg connect should distinguish between connecting 
> to function objects in general, and connecting to pointers to (static) 
> functions; disallow the former, and allow the latter.)

Stateless lambdas can decay to plain functions (connectToStatic). I don't 
think this is workable.

On the other hand, we can add a connect() overload that indicates the minimum 
class level that the receiver object must be, otherwise disconnection happens. 
See below for an idea.

> > Those are lambdas that, by capturing [this], are effectively member
> > functions in disguise, but there's no way we can tell that. Invoking
> > those callbacks after the members used in bodies of the lambdas have been
> > destroyed is UB.
> 
> But that's the reason for passing the 3rd argument, isn't it? To make 
> sure that deleting `this` will disconnect. (At least, that's the idea; 
> at the moment it's not foolproof, thus this thread...)

But only when this has completed destruction, not when it has started or at 
some point in the middle. It is entirely allowed for the lambda to only depend 
on the sender and require no resource from the receiver, such as a logging 
function.

connect() currently can't know when to disconnect if the receiver isn't a PMF.

> > Even the distinction between PMFs and others could be a pitfall. Take the
> > first example from QWinEventNotifier and let's say the original code was
> > 
> >      QObject::connect(notifier, &QWinEventNotifier::activated,
> >                       this, &QWinRegistryKey::valueChanged);
> > 
> > 
> > That code would have had the benefit of the protection. But then after
> > some bug-fixing or additional functionality, it became the lambda above,
> > which does not have the protection. That could lead to a subtle new
> > bug[*] that may escape detection during unit-testing and get found only
> > by users. 
> > [*] Right now, we don't have the protection against calling a PMF in a
> > destroyed sub-object, but we have an *enforcement* in debug mode, so if
> > the delivery could have hit the PMF before the change, it should have
> > been seen and fixed. Since we have the enforcement, I think that if we
> > provide the functionality, it should be active for PMFs by default.
> 
> 
> That's a separate can of worms, and also, once more, why people should 
> always use the 4-arg connect (and avoid connecting to complicated lambdas).

No dispute there.

However, the issue is that you can go from a 4-arg connect() where the 4th 
argument is a PMF and thus has the protection to a 4-arg connect() where the 
4th is not a PMF and thus loses the protection.

As I said, I want to add a feature to allow:

    QObject::connect(notifier, &QWinEventNotifier::activated, this,
        [registerForNotification](QWinEventNotifer *self) {
            emit self->valueChanged();
            registerForNotification();
        });

This above *would* have retained the protection. And you don't need to use the 
pointer, but having that extra first argument would tell QObject when the 
disconnection should happen.

-- 
Thiago Macieira - thiago.macieira (AT) intel.com
  Principal Engineer - Intel Platform Engineering Group
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/pkcs7-signature
Size: 5150 bytes
Desc: not available
URL: <http://lists.qt-project.org/pipermail/development/attachments/20250917/7092906c/attachment-0001.bin>


More information about the Development mailing list