Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Unexpected disconnect of QObject connections
Forum Updated to NodeBB v4.3 + New Features

Unexpected disconnect of QObject connections

Scheduled Pinned Locked Moved Unsolved General and Desktop
7 Posts 4 Posters 154 Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • DuBuD Offline
    DuBuD Offline
    DuBu
    wrote last edited by DuBu
    #1

    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.

    jsulmJ 1 Reply Last reply
    0
    • DuBuD DuBu

      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.

      jsulmJ Offline
      jsulmJ Offline
      jsulm
      Lifetime Qt Champion
      wrote last edited by
      #2

      @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.

      https://forum.qt.io/topic/113070/qt-code-of-conduct

      DuBuD 1 Reply Last reply
      1
      • jsulmJ jsulm

        @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.

        DuBuD Offline
        DuBuD Offline
        DuBu
        wrote last edited by
        #3

        @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.

        JonBJ 1 Reply Last reply
        0
        • DuBuD DuBu

          @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.

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote last edited by JonB
          #4

          @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).

          DuBuD 1 Reply Last reply
          3
          • JonBJ JonB

            @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).

            DuBuD Offline
            DuBuD Offline
            DuBu
            wrote last edited by
            #5

            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.

            JonBJ 1 Reply Last reply
            0
            • DuBuD DuBu

              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.

              JonBJ Offline
              JonBJ Offline
              JonB
              wrote last edited by
              #6

              @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 via QObject::setObjectName().

              1 Reply Last reply
              0
              • Axel SpoerlA Offline
                Axel SpoerlA Offline
                Axel Spoerl
                Moderators
                wrote last edited by Axel Spoerl
                #7

                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.)

                Software Engineer
                The Qt Company, Oslo

                1 Reply Last reply
                2

                • Login

                • Login or register to search.
                • First post
                  Last post
                0
                • Categories
                • Recent
                • Tags
                • Popular
                • Users
                • Groups
                • Search
                • Get Qt Extensions
                • Unsolved