Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How to connect signal slots between many objects



  • Hi, all
    When there are multiple threads in the application, and each thread has several QObject objects, how can I reasonably connect the signal slots between arbitrary objects? I mean to minimize the coupling between threads.
    My current approach is to define a global singleton class (Signal Station). I don't know if there is a better way. It is best to have a demo that can be referred to.
    Best regards!


  • Lifetime Qt Champion

    Hi,

    Since you say they are unrelated, can you explain why they should be connected to each other ?



  • @SGaist Hi, thank you very much for your reply, and apologize for my poor English.
    I have many private objects in every thread, These objects will have many signal slots that need to be connected.
    The simplified framework is shown below:
    qt.png



  • @SGaist
    There is a complicated parent-child relationship between objects in the same thread, which makes it difficult to connect child objects of different threads. I don't know how to deal with this architecture, so I defined a global singleton signal transfer station, through this transfer station to realize the object communication of different threads. However, the coupling of this method is still quite large, and the signal needs to be transferred many times, I feel that this is wrong. I want to know how you realize the communication of sub-objects between threads. Thank you!



  • This is the code I found on the web, the purpose is to pass messages directly between multi-layer forms.
    I don't know if this code is the correct way to achieve communication between multiple sub-objects in multiple threads.

    Header

    #pragma once
    #include <qobject.h>
    class obesrverApater;
     
    struct relationData
    {
    	QString type;
    	QObject *receiver;
    	obesrverApater *obesrverApater;
    };
     
    class obesrverApater : public QObject
    {
    	Q_OBJECT
    public:
    	explicit obesrverApater(QObject *parent = 0);
    signals:
    	void notify(const QStringList&);
    };
     
    class GlobalObserver : public QObject
    {
    	Q_OBJECT
    public:
    	static GlobalObserver* GetInstance();
    	static GlobalObserver *m_pInstance;
    	void attach(const QString & type, QObject * receiver, const char * method);
    	void detach(const QString &type, const QObject* reciver);
    	void notify(const QString & type, const QStringList & params);
    private:
    	GlobalObserver();
    	~GlobalObserver();
    private:
    	QList<relationData*> m_oRelationList;
    };
    

    Source

    #include "GlobalObserver.h"
     
    GlobalObserver* GlobalObserver::m_pInstance = nullptr;
     
    GlobalObserver::GlobalObserver()
    {
    }
    GlobalObserver::~GlobalObserver()
    {
    }
     
    GlobalObserver* GlobalObserver::GetInstance()
    {
    	if (m_pInstance == nullptr) {
    		m_pInstance = new GlobalObserver();
    	}
    	return m_pInstance;
    }
     
    void GlobalObserver::attach(const QString& type, QObject* receiver, const char *method)
    {
    	obesrverApater* oa = new obesrverApater();
    	connect(oa, SIGNAL(notify(const QStringList&)), receiver, method);
    	relationData *data = new relationData();
    	data->type = type;
    	data->receiver = receiver;
    	data->obesrverApater = oa;
    	m_oRelationList.append(data);
    }
     
    void GlobalObserver::detach(const QString & type, const QObject * receiver)
    {
    	QList<relationData*>::iterator iter = m_oRelationList.begin();
     
    	while (iter != m_oRelationList.end()){
    		if ((*iter)->type.compare(type) == 0 && (*iter)->receiver == receiver){
    			relationData *data = *iter;
    			m_oRelationList.removeOne((*iter));
    			delete data->obesrverApater;
    			delete data;
    			return;
    		}
    		iter++;
    	}
    }
     
    void GlobalObserver::notify(const QString & type, const QStringList& params)
    {
    	QList<relationData*>::iterator iter = m_oRelationList.begin();
    	while (iter != m_oRelationList.end()){
    		if ((*iter)->type.compare(type) == 0){
    			emit(*iter)->obesrverApater->notify(params);
    		}
    		iter++;
    	}
    }
    

    Test code

    // Attach
    GlobalObserver::GetInstance()->attach("export", this, SLOT(sltAddExportItem(const QStringList&)));
    // Detach
    GlobalObserver::GetInstance()->detach("export", this);
    // Emit signal
    QStringList params;
    GlobalObserver::GetInstance()->notify("export", params);
    


  • I had a similar problem once. My solution was to write a central data model class, which had all the data that needed to be shared between multiple objects on multiple layers.
    The result was that instead of passing data between multiple objects, I would always pass data to the data model first, and the data model offered centralized notification for anyone interested in a specific data point. The web of inter-dependencies became a much more manageable hub, with the side benefit of centralized logging.

    I can't really say whether my solution would really fit your use case, but I hope it helps.



  • I want to achieve communication between multiple layers of sub-objects in a low-coupling way.

    class Grandchild1 : public QObject
    {
        Q_OBJECT
    public:
        explicit Grandchild1(QObject *parent = nullptr);
    signals:
        some signals
    public slots:
        some slots
    };
    
    class Grandchild2 : public QObject
    {
        Q_OBJECT
    public:
        explicit Grandchild2(QObject *parent = nullptr);
    signals:
        some signals
    public slots:
        some slots
    };
    
    class Grandchild3 : public QObject
    {
        Q_OBJECT
    public:
        explicit Grandchild3(QObject *parent = nullptr);
    signals:
        some signals
    public slots:
        some slots
    };
    
    class Child1 : public QObject
    {
        Q_OBJECT
    public:
        explicit Child1(QObject *parent = nullptr);
    private:
        Grandchild1 grandchild1;
        Grandchild2 grandchild2;
        Grandchild3 grandchild3;
    signals:
        some signals
    public slots:
        some slots
    };
    
    Class Child2;
    
    Class Child3;
    
    class Object1 : public QObject
    {
        Q_OBJECT
    public:
        explicit Object1(QObject *parent = nullptr);
    private:
        Child1 child1;
        Child2 child2;
        Child3 child3;
    signals:
        some signals
    public slots:
        some slots
    };
    

    qt2.png


  • Lifetime Qt Champion

    What kind of messages are you going to exchange between these various unrelated objects ?



  • @SGaist
    I'm very sorry, I don’t quite understand what "What kind of messages" means~
    @Asperamanca’s solution explains my doubts very well. If there is no other solution, I will follow Asperamanca ’s suggestion.
    Best regards!



  • @Asperamanca Hi, thank you very much!
    Your solution is the answer I want, but my English is too bad, and I can't always explain my problem.
    Take the communication between child1 and child2 as an example, is the code like this?

    CentralModel

    class CentralModel : public QObject, public Singleton
    {
        Q_OBJECT
    public:
        explicit CentralModel(QObject *parent = nullptr) : QObject(parent) {}
        ~CentralModel() {}
    
    private:
        QVariant m_data; //!< shared data between child1 and child2
        QReadWriteLock m_dataLock; //!< lock for m_data
    
    public:
        QVariant data() //!< get data
        {
            QReadLocker locker(&m_dataLock);
            return m_data;
        }
        void setData(const QVariant value) //!< set data
        {
            QWriteLocker locker(&m_dataLock);
            if (m_data != value) {
                m_data = value;
                emit dataChanged(value);
            }
        }
    
    signals:
        void dataChanged(const QVariant value);
    };
    

    Child1

    class Child1 : public QObject,
    {
        Q_OBJECT
    public:
        explicit Child1(QObject *parent = nullptr) : QObject(parent) {}
        ~Child1() {}
    
    public:
        QVariant func()
        {
            CentralModel::getInstance()->setData(3.14);
        }
    };
    

    Child2

    class Child2 : public QObject,
    {
        Q_OBJECT
    public:
        explicit Child2(QObject *parent = nullptr) : QObject(parent)
        {
            QObject::connect(CentralModel::getInstance(), SIGNAL(dataChanged(const QVariant)), SLOT(onDataChanged(const QVariant)));
        }
        ~Child2() {}
    
    private slots:
        void onDataChanged(const QVariant value)
        {
            qDebug() << value.toDouble();
        }
    };
    
    


  • I would only design the central model as a singleton if you are sure you'll not need a separate model in the future (e.g. old project state vs current project state).
    You sample code doesn't tell me what you mean by "QVariant data()". If it was just an example, and in reality you'll have multiple properties that should work fine. What I would not do is put every information you have inside the data() and notify everyone if anything changes.

    One nice trick (if you need it): If you store the data of the whole data model in a separate, copyable class (just place an instance in the DataModel QObject class and use it there), you can always create snapshots of older states of your project data. Also, you can easily implement transaction logic (multiple changes are only applied together once you are ready to push them).



  • @Asperamanca
    Yes, "QVariant data()" is just an example, there are many different types of data and signals in actual projects.



  • @Asperamanca said in How to connect signal slots between many objects:

    One nice trick (if you need it): If you store the data of the whole data model in a separate, copyable class (just place an instance in the DataModel QObject class and use it there), you can always create snapshots of older states of your project data. Also, you can easily implement transaction logic (multiple changes are only applied together once you are ready to push them).

    Do you mean like this?

    class ExampleModel1 : public QObject //!< Copyable
    {
        Q_OBJECT
    public:
        explicit ExampleModel1(QObject *parent = nullptr);
        ~ExampleModel1();
    private:
        QVariant exampleData1;
    public:
        QVariant getExampleData1();
        void setExampleData1(const QVariant value);
    signals:
        void exampleData1Changed(const QVariant value);
    };
    
    class ExampleModel2 : public QObject //!< Copyable
    {
        Q_OBJECT
    public:
        explicit ExampleModel2(QObject *parent = nullptr);
        ~ExampleModel2();
    private:
        QVariant exampleData2;
    public:
        QVariant getExampleData2();
        void setExampleData2(const QVariant value);
    signals:
        void exampleData2Changed(const QVariant value);
    };
    
    class CentralModel : public QObject, public Singleton  //!< Singleton, Not copyable
    {
        Q_OBJECT
    public:
        explicit CentralModel(QObject *parent = nullptr);
        ~CentralModel();
    
    public:
        ExampleModel1 *model1;
        QReadWriteLock lock1; // lock for model1
        
        ExampleModel2 *model2;
        QReadWriteLock lock2;  // lock for model2
    };
    


  • @tovax
    QObject is never copyable. So the data has to be in classes not derived from QObject. If your only access to these data classes is via you (QObject-derived) Data model, then you have full control when data is changed, and the data model can reliably fire notification signals.


  • Lifetime Qt Champion

    @tovax said in How to connect signal slots between many objects:

    @SGaist
    I'm very sorry, I don’t quite understand what "What kind of messages" means~

    I meant what kind of data are you going to pass around ? Depending on that you might want to consider a publish/subscribe system like mqtt and the corresponding QtMQTT module.



  • @Asperamanca
    I will try to implement the central model in the project based on your suggestions. Maybe my understanding is not enough at present, but I think I will have a deeper understanding in the process of doing it.
    Best regards!

    class CentralData
    {
    public:
        CentralData();
        ~CentralData();
        QVariant exampleData1;
        QVariant exampleData2;
        QVariant exampleDataN;
    };
    
    class CentralModel : public QObject
    {
    public:
        CentralModel();
        ~CentralModel();
    
    private:
        CentralData centralData;
    
    public:
        QVariant getExampleData1();
        void setExampleData1(const QVariant value);
        QVariant getExampleData2();
        void setExampleData2(const QVariant value);
        QVariant getExampleDataN();
        void setExampleDataN(const QVariant value);
    
    signals:
        void exampleData1Changed(const QVariant value);
        void exampleData2Changed(const QVariant value);
        void exampleDataNChanged(const QVariant value);
    };
    


  • @SGaist Hi, thank you very much for your patience.
    Data types supported by QVariant, and some custom structures. I will carefully read the link you gave.



  • @tovax said in How to connect signal slots between many objects:

    @Asperamanca
    I will try to implement the central model in the project based on your suggestions. [...]

    Yes, that looks about like what I was suggesting.
    A few notes:

    • If you ever set the centralData (e.g. to revert to an old state), you'll need a mechanism to trigger all change signals that apply
    • If all your data is QVariant, you could store it in a single container, and access it via Enum:
    QVariant getData(const EDataKey eKey) const;
    

    However, signals are tricky in this case. You could have a single signal that passes the changed EDataKey (or a List or Set of changed keys for a sum change notification), but it would mean that everyone who is interested in data change will get all notifications, and needs to discard those that are not relevant. You could still make single signals and only one setData/getData, but this would mean some ugly switches in your code, and an inconsistent interface.

    But this brings me to another question:

    • Why make all data QVariant? You lose the benefits of type safety. What do you gain?


  • @Asperamanca Hi,
    I have two kinds of data, the first is the parameter obtained from the database, and its type is QVariant, which is converted when it is used; the second is my customized structure data.


Log in to reply