For communication problems between objects, many frameworks use callback function classes to solve them. QT uses signal-slot to solve communication problems between objects. As long as you inherit the QObject class, you can use the signal-slot mechanism. Signal-slots are very simple and flexible to use, and transmit and receive objects to achieve decoupling. The object transmitting the signal does not need to pay attention to which objects need to receive signals, but only needs to transmit signals when the state changes; the object receiving the signal does not need to pay attention to when the signal is transmitted, but only needs to pay attention to the implementation of the slot function. Compared with callback functions, signal-slot efficiency will be lower. Generally speaking, using the signal-slot mechanism is 10 times slower than directly calling the slot function.
Signal-Slot link type
-
Qt::AutoConnection(Default type)
If the transmitted signal is in the same thread as the receiving object, this type is handled the same as Qt::DirectConnection, otherwise it is the same as Qt::QueuedConnectio. -
Qt::DirectConnection
The slot function is called immediately when the signal is transmitted (as is the same as the callback function). The slot function is executed in the thread that transmits the signal. -
Qt::QueuedConnection
The slot function is executed within the receiver thread. The slot function is executed when the control of the event loop is handed over to the receiving thread. useQt::QueuedConnectionWhen typed, the parameter types of the signal and slot must be a known type of the QT meta object system. Because QT needs to copy and store parameters in the background to events. If the parameter type is not a known type of the QT meta object system, the following error will be triggered:QObject::connect: Cannot queue arguments of type 'MyType'
At this time, before establishing the link, you need to call the qRegisterMetaType() method class to register the data type.
-
Qt::BlockingQueuedConnection
After the signal is transmitted, the current thread blocks until the thread of the slot function is awakened and executed.Notice:Using this type will result in deadlock when the object of the transmit signal and the receiving slot function is within the same thread. -
Qt::UniqueConnection
This type can be used in conjunction with the above type using OR operation. After setting to this type, the same signal can only be linked to the slot function of the same object once. If the link already exists, the link will not be established again, connect() returns false. Notice:The slot functions corresponding to this type can only be member functions of the class, and cannot use lambda expressions and non-member functions. -
Qt::SingleShotConnection
This type can be used in conjunction with the above type using OR operation. After setting to this type, the slot function is called only once. After the signal is transmitted, the connection between the signal and the slot will be automatically disconnected. This type was introduced after QT 6.0.QObject::connect() itself is thread-safe, butQt::DirectConnectionWhen type, if the sender and receiver of the signal are not in the same thread, it is not thread-safe.
Signal-slot link method
A signal can link multiple slot functions, a slot function can link multiple signals, and a signal can also link directly to another signal. If a signal links multiple slot functions, when a signal is transmitted, the slot functions are called and executed in accordance with the sequence of linking.
QT provides 2 linking methods: string-based syntax and function-based syntax.
String-based syntax:
QMetaObject::Connection QObject::connect(const QObject **sender*, const char **signal*, const QObject **receiver*, const char **method*, Qt::ConnectionType *type* = Qt::AutoConnection)
// Macros SIGNAL() and SLOT() are required to declare signals and slot functions
Function-based syntax:
QMetaObject::Connection QObject::connect(const QObject **sender*, const QMetaMethod &*signal*, const QObject **receiver*, const QMetaMethod &*method*, Qt::ConnectionType *type* = Qt::AutoConnection)
Their differences are as follows:
Based on strings | Based on functions | |
---|---|---|
Type Check | Runtime | Compilation period |
Support implicit type conversion | no | yes |
Supports linking signals using lambda expressions | no | yes |
The parameters of the support slot are fewer than the number of parameters of the signal | yes | no |
Support linking C++ functions to QML functions | yes | no |
-
Type checking and implicit type conversion
The syntax based on strings depends on the reflection function of the meta-object system, and uses string matching to check signals and slot functions, which have the following limitations:
-
Link errors can only be checked when running;
-
Implicit type conversion cannot be used;
-
Cannot resolve type definitions and namespaces;
Function-based syntax is checked by the compiler. The compiler can check for link errors during the compilation period, and supports implicit type conversion, and can also identify different names of the same type (i.e. type definition).
auto slider = new QSlider(this); auto doubleSpinBox = new QDoubleSpinBox(this); // OK: The compiler converts int to double connect(slider, &QSlider::valueChanged, doubleSpinBox, &QDoubleSpinBox::setValue); // ERROR: The string cannot contain conversion information connect(slider, SIGNAL(valueChanged(int)), doubleSpinBox, SLOT(setValue(double)); auto audioInput = new QAudioInput(QAudioFormat(), this); auto widget = new QWidget(this); // OK connect(audioInput, SIGNAL(stateChanged(QAudio::State)), widget, SLOT(show())); // ERROR: The namespace cannot be used, the string "State" does not match "QAudio::State" using namespace QAudio; connect(audioInput, SIGNAL(stateChanged(State)), widget, SLOT(show())); // ...
-
Linking signals using lambda expressions
Function-based syntax supports C++11 lambda expressions, and also supports standard functions, non-member functions, and pointers to functions. But for greater readability, signals should be linked to slot functions, lambda expressions, and other signals.
class TextSender : public QWidget { Q_OBJECT QLineEdit *lineEdit; QPushButton *button; signals: void textCompleted(const QString& text) const; public: TextSender(QWidget *parent = nullptr); }; TextSender::TextSender(QWidget *parent) : QWidget(parent) { lineEdit = new QLineEdit(this); button = new QPushButton("Send", this); // Use lambda expression as slot function connect(button, &QPushButton::clicked, [=] { emit textCompleted(lineEdit->text()); }); // ... }
-
Linking C++ objects with QML objects
String-based syntax can link C++ objects with QML objects, because the QML type is only parsed at runtime and cannot be recognized by the C++ compiler.
// document Rectangle { width: 100; height: 100 signal qmlSignal(string sentMsg) function qmlSlot(receivedMsg) { ("QML received: " + receivedMsg) } MouseArea { : parent onClicked: qmlSignal("Hello from QML!") } } // C++ class file class CppGui: public QWidget { Q_OBJECT QPushButton *button; signals: void cppSignal(const QVariant& sentMsg) const; public slots: void cppSlot(const QString& receivedMsg) const { qDebug() << "C++ received:" << receivedMsg; } public: CppGui(QWidget *parent = nullptr) : QWidget(parent) { button = new QPushButton("Click Me!", this); connect(button, &QPushButton::clicked, [=] { emit cppSignal("Hello from C++!"); }); } }; auto cppObj = new CppGui(this); auto quickWidget = new QQuickWidget(QUrl(""), this); auto qmlObj = quickWidget->rootObject(); // QML signal is linked to C++ slot function connect(qmlObj, SIGNAL(qmlSignal(QString)), cppObj, SLOT(cppSlot(QString))); // C++ signal is linked to QML slot function connect(cppObj, SIGNAL(cppSignal(QVariant)), qmlObj, SLOT(qmlSlot(QVariant)));
-
Number of parameters of slot function
Generally speaking, the parameter type of the slot function is consistent with the signal declaration, and the number is equal to or less than the parameters of the signal. String-based syntax provides a workaround: if the parameter function has default parameters, the transmitted signal can omit these parameters. When the parameters of the transmitted signal are less than those of the slot function, QT will use the default parameters of the slot function.
Function-based syntax cannot directly link such signals-slots, but signals can be linked to lambda expressions, and slot functions can be called within the expression.
public slots: void printNumber(int number = 42) { qDebug() << "Lucky number" << number; } DemoWidget::DemoWidget(QWidget *parent) : QWidget(parent) { // OK: Use the default value when calling printNumber() 42 connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(printNumber())); // ERROR: The compiler requires parameter matching connect(qApp, &QCoreApplication::aboutToQuit, this, &DemoWidget::printNumber); }
-
Signal-Slot Reload
For overloaded signals or slots, the string-based link syntax can display the declared parameter type, but the function-based link syntax cannot tell the compiler which strength to use to link. At this time, the parameter type needs to be specified through the qOverload function.
// Definition of slot function overload QLCDNumber::display(int) QLCDNumber::display(double) QLCDNumber::display(QString) auto slider = new QSlider(this); auto lcd = new QLCDNumber(this); // S string-based link syntax connect(slider, SIGNAL(valueChanged(int)), lcd, SLOT(display(int))); // Function-based link syntax connect(slider, &QSlider::valueChanged, lcd, qOverload<int>(&QLCDNumber::display));
Signal-Slot automatic link
Signal slots can be linked manually or automatically during compilation or runtime. QT's meta-object system (QMetaObject) can automatically match slots matching names based on signals. When declaring and implementing the slot function as follows, uic (User Interface Compiler) will automatically establish a link between the signal and the slot in the setupUi() function. (This is the case with slot functions automatically created through the form interface of QT Creator)
void on_<object name>_<signal name>(<signal parameters>);
Note: When the widgets in form is renamed, the name of the slot function also needs to be modified accordingly.
You can also use the following function to enable automatic matching of signals and slots:
QMetaObject::connectSlotsByName(this);
Get the signal transmitter
Calling the function QObject::sender() in the slot function can get the QObject object pointer of the signal transmitter. If you know the type of the signal transmitter, you can convert the QObject pointer to a pointer of the object of the deterministic type and then use this deterministic class interface function.
// btnProperty is the QPushButton type, as the transmitter of the signal,
// This method is a slot function of the click() signal and uses automatic linking
void Widget::on_btnProperty_clicked()
{
//The transmitter who obtains the signal
QPushButton *btn= qobject_cast<QPushButton*>(sender());
bool isFlat= btn->property("flat").toBool();
btn->setProperty("flat", !isFlat);
}
If the slot function is a lambda expression, it is easier to obtain the signal transmitter, just pass parameters.
connect(action, &QAction::triggered, engine,
[=]() { engine->processAction(action->text()); });
Unconnecting the signal to the slot
The function disconnect() is used to disconnect the signal and the slot. It has 2 member function forms and 4 static function forms. There are the following ways to use it. In the schematic code, myObject is the object that transmits the signal, and myReceiver is the object that receives the signal.
- Unconnect all signals from one transmitter
// Static function form disconnect(myObject, nullptr, nullptr, nullptr); // Member function form myObject->disconnect();
- Unlock all connections to a specific signal
// Static function form disconnect(myObject, SIGNAL(mySignal()), nullptr, nullptr); // Member function form myObject->disconnect(SIGNAL(mySignal()));
- Unlock all connections to a specific recipient
// Static function form disconnect(myObject, nullptr, myReceiver, nullptr); // Member function form myObject->disconnect(myReceiver);
- Unconnecting a specific signal to the slot
// Static function form disconnect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);
Some rules for signal-slots
-
Inheriting the QObject class
Signal-slots can only be used if the QObject class is inherited. When there is multiple inheritance, QObject must be the first inheritance class, because moc always checks whether the first inherited class is QObject and if not, the moc file will not be generated. In addition, the template class cannot use the Q_OBJECT macro.
// WRONG class SomeTemplate<int> : public QFrame { Q_OBJECT ... signals: void mySignal(int); }; // correct class SomeClass : public QObject, public OtherClass { ... };
-
Function pointers cannot be used as parameters for signals or slots
In many cases, inheritance or virtual functions can be used instead of function pointers.
class SomeClass : public QObject { Q_OBJECT public slots: void apply(void (*apply)(List *, void *), char *); // WRONG }; // correct typedef void (*ApplyFunction)(List *, void *); class SomeClass : public QObject { Q_OBJECT public slots: void apply(ApplyFunction, char *); };
-
When the parameters of a signal or slot are enumerated quantities, it must be fully qualified.
This is mainly aimed at string-based link syntax, because it relies on string matching to identify data types.
class MyClass : public QObject { Q_OBJECT enum Error { ConnectionRefused, RemoteHostClosed, UnknownError }; signals: void stateChanged(MyClass::Error error); };
-
Nested classes will not be able to use signals or slots
class A { public: class B { Q_OBJECT public slots: // WRONG void b(); }; };
-
Inverse return type of signal or slot cannot be referenced
Although signals or slots can have return types, their return references will be treated as void type.
-
The part of a class declares a signal or slot can only declare a signal or slot.
The moc compiler will check the statements in this section
Integrated third-party signal-slot
Integrating the third-party signal-slot mechanism requires avoiding conflicts between signal, slots, emit keywords and third parties (such as Boost). It is mainly through configuration that moc does not use signal, slots, emit keywords. The following configuration is required: For projects using CMake, you need to add them in the project file
target_compile_definitions(my_app PRIVATE QT_NO_KEYWORDS)
For projects using qmake, you need to add them in the .pro file
CONFIG += no_keywords
The corresponding keywords in the source file should be replaced by Q_SIGNALS (Q_SIGNAL), Q_SLOTS (Q_SLOT), Q_EMIT.
The public API of Qt-based libraries should use the keywords Q_SIGNALS and Q_SLOTS, otherwise it will be difficult to use such libraries in projects that define QT_NO_KEYWORDS. You can set the preprocessor definition QT_NO_SIGNALS_SLOTS_KEYWORDS when building the library to enforce this restriction.
Signal-slot performance
The signal slot mechanism of QT is not as good as template-based solutions (such as boost::signal2 or custom implementation). Because the signal-slot of QT depends on the meta-object system, the compiler (MOC) generates additional code that will dynamically look up the association between the signal and the slot at runtime; the parameters are encapsulated by QVariant, adding runtime checks. The cost of transmitting a signal by a template-based solution is about the cost of calling 4 functions, but QT costs about the cost of calling 10 functions. Although signal transmission increases time overhead, these overheads are negligible relative to the execution of code in the slot. The signal-slot of QT is not suitable for scenarios where high performance needs are required (for example, scenarios where core algorithms, high-frequency event processing, game loops, audio processing, etc. require millisecond response).
【refer to】:
Why Does Qt Use Moc for Signals and Slots?
Signals and Slots Across Threads
Differences between String-Based and Functor-Based Connections | Qt 6.8
Signals & Slots | Qt Core 6.8.3