1. What are signals and slots?
Signals and slots are a variant of the publish-subscribe mode. We can understand it as:
- Signal:An event source (Publisher), when an event occurs, it triggers (emit) the signal.
- groove:An event processor (Subscriber) will be called when a signal is triggered to complete the specific response task.
For example:
- A button is clicked and the slot function is responsible for handling click events.
- A timer triggers the signal, and the slot function completes the timing task.
In C++, we can use templates and function objects to simulate this mechanism.
2. Design objectives
Functions implemented
- Allows multiple slots to connect to the same signal.
- Supports dynamic addition and removal of slots.
- When a signal is triggered, all connected slots are automatically called.
- Use templates to support different signal parameter types.
- Flexible registration of normal functions, class member functions and Lambda expressions as slots.
3. Module design
(1) Signal template class
Signal
It is the core class we design to manage the connection and triggering of signals and slots. It requires the following functions:
-
connect
:Register a slot function to the signal. -
disconnect
:Dynamically remove slot functions through unique ID. -
emit
:Trigger signal to call all registered slots.
Below isSignal
Complete implementation of the class:
#ifndef SIGNAL_H #define SIGNAL_H #include <unordered_map> // hash table for storage slots#include <functional> // Used to store slot functions of any form#include <iostream> // Used to output debugging information // Signal classtemplate <typename... Args> class Signal { public: using SlotType = std::function<void(Args...)>; // Define the slot type using SlotID = int; // Unique identifier for slot // Connect a slot to return the unique ID of the slot SlotID connect(SlotType slot) { SlotID id = nextID++; slots[id] = slot; //Save the slot into the hash table return id; } // Disconnect a slot and pass its unique ID void disconnect(SlotID id) { auto it = (id); if (it != ()) { (it); // Remove slot from hash table } } // Trigger signal, calling all connected slots void emit(Args... args) const { for (const auto &pair : slots) { (args...); // Call slot function } } private: std::unordered_map<SlotID, SlotType> slots; // The hash table of the storage slot SlotID nextID = 0; // Counter used to generate a unique ID}; #endif // SIGNAL_H
(2) Example of connection slot
We useSignal
The template class connects multiple slots, including ordinary functions, Lambda expressions and class member functions.
#include "" #include <iostream> #include <string> // Normal functions as slotsvoid slot1(const std::string &message) { std::cout << "Slot 1 Received message: " << message << std::endl; } // Normal functions as slotsvoid slot2(const std::string &message) { std::cout << "Slot 2 received the message: " << message << std::endl; } // Test class, own slotclass TestClass { public: // Member functions as slots void classSlot(const std::string &message) { std::cout << "TestClass::classSlot received the message: " << message << std::endl; } };
(3) Main program example
Through the main program, we test the following functions:
- Register normal functions, Lambda expressions, and member functions to signals.
- Trigger signal, calling all slots.
- Dynamically disconnect a slot and verify the slot removal function.
#include "" #include <iostream> #include <string> int main() { // Create a signal Signal<std::string> signal; // Connect ordinary functions to signals auto id1 = (slot1); auto id2 = (slot2); // Create a class instance and connect member functions to signals TestClass obj; auto id3 = ([&obj](const std::string &message) { (message); }); // The first time the signal is triggered, all slots will be called std::cout << "First trigger signal:" << std::endl; ("Hello, signal and slot!"); //Slot 1 from the signal std::cout << "\nAfter disconnecting slot 1, the second trigger signal:" << std::endl; (id1); // For the second trigger signal, only slot 2 and member function slot will be called ("This is the second message!"); return 0; }
4. Operation results
After running the program, the output is as follows:
The first trigger signal:
Slot 1 Message received: Hello, signal and slot!
Slot 2 Received message: Hello, signal and slot!
TestClass::classSlot Received message: Hello, signal and slot!After disconnecting slot 1, the second trigger signal is triggered:
Slot 2 Message received: This is the second message!
TestClass::classSlot Received message: This is the second message!
5. Code parsing
-
Slot management
- Each slot function passes
connect
The method is registered with the signal, and the signal will assign a unique identifier to each slot (SlotID
)。 - The slot function is stored in
std::unordered_map
Medium, the key isSlotID
, the value is the slot function.
- Each slot function passes
-
The trigger of the signal
- Call
emit
When a method, all registered slots will be traversed and they will be called in turn.
- Call
-
Dynamic removal of slots
- Unique identifier through slot (
SlotID
), calldisconnect
Method, you can remove the specified slot from the signal.
- Unique identifier through slot (
-
Supports multiple types of slots
- use
std::function
Storage slots can easily support ordinary functions, Lambda expressions and class member functions.
- use
6. Features and advantages
advantage
-
Modular design:
-
Signal
Class implements signal management and triggering, independent and easy to use.
-
-
Support diversification tanks:
- Supports both normal functions and member functions and Lambda expressions.
-
high performance:
- use
std::unordered_map
The time complexity of storage slots for addition, removal and triggering is O(1).
- use
Features
- Lightweight implementation:Rely on only the C++ standard library, no additional framework is required.
- Template design:It can be adapted to signals and slots of any parameter type.
7. Application scenarios
-
Event-driven development:
- Such as GUI button clicks, window closing events and other scenarios.
-
Decoupling module:
- In the observer mode, signals and slots are used instead of observer notification mechanism.
-
Callback mechanism:
- Replaces the traditional callback function method and provides more flexible signal and slot functions.
8. Summary
Through this article, we have implemented a lightweight, fully functional signal and slot system. It draws on Qt's design ideas, but is lighter and more flexible. This design can be easily applied to any pure C++ project, especially suitable for event-driven and decoupled communication scenarios. If you need to extend to a multi-threaded environment, you can add thread safety mechanisms on this basis, such asstd::mutex
。
You can use this code as a basis to further transform and optimize it to create an efficient signal and slot system that meets your needs!
This is the article about using C++ to implement the signal and slot mechanism functions similar to Qt. For more related content on C++ to implement the signal and slot mechanism, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!