使用C++實(shí)現(xiàn)類似Qt的信號(hào)與槽機(jī)制功能
1. 什么是信號(hào)與槽?
信號(hào)與槽是一個(gè)發(fā)布-訂閱模式的變種。我們可以將它理解為:
- 信號(hào): 一個(gè)事件源(Publisher),當(dāng)某個(gè)事件發(fā)生時(shí),它會(huì)觸發(fā)(emit)信號(hào)。
- 槽: 一個(gè)事件處理器(Subscriber),當(dāng)信號(hào)觸發(fā)時(shí),它會(huì)被調(diào)用,完成具體的響應(yīng)任務(wù)。
例如:
- 一個(gè)按鈕點(diǎn)擊時(shí)發(fā)出信號(hào),槽函數(shù)負(fù)責(zé)處理點(diǎn)擊事件。
- 一個(gè)定時(shí)器觸發(fā)信號(hào),槽函數(shù)完成定時(shí)任務(wù)。
在 C++ 中,我們可以用模板和函數(shù)對(duì)象來模擬這種機(jī)制。
2. 設(shè)計(jì)目標(biāo)
實(shí)現(xiàn)的功能
- 允許多個(gè)槽連接到同一個(gè)信號(hào)。
- 支持動(dòng)態(tài)添加和移除槽。
- 觸發(fā)信號(hào)時(shí),自動(dòng)調(diào)用所有已連接的槽。
- 使用模板支持不同的信號(hào)參數(shù)類型。
- 靈活注冊(cè)普通函數(shù)、類成員函數(shù)和 Lambda 表達(dá)式作為槽。
3. 模塊設(shè)計(jì)
(1)Signal 模板類
Signal
是我們?cè)O(shè)計(jì)的核心類,用于管理信號(hào)與槽的連接和觸發(fā)。它需要實(shí)現(xiàn)以下功能:
connect
: 注冊(cè)一個(gè)槽函數(shù)到信號(hào)。disconnect
: 通過唯一 ID 動(dòng)態(tài)移除槽函數(shù)。emit
: 觸發(fā)信號(hào),調(diào)用所有已注冊(cè)的槽。
下面是 Signal
類的完整實(shí)現(xiàn):
#ifndef SIGNAL_H #define SIGNAL_H #include <unordered_map> // 用于存儲(chǔ)槽的哈希表 #include <functional> // 用于存儲(chǔ)任意形式的槽函數(shù) #include <iostream> // 用于輸出調(diào)試信息 // 信號(hào)類 template <typename... Args> class Signal { public: using SlotType = std::function<void(Args...)>; // 定義槽的類型 using SlotID = int; // 槽的唯一標(biāo)識(shí)符 // 連接一個(gè)槽,返回槽的唯一 ID SlotID connect(SlotType slot) { SlotID id = nextID++; slots[id] = slot; // 將槽存入哈希表 return id; } // 斷開一個(gè)槽,通過其唯一 ID void disconnect(SlotID id) { auto it = slots.find(id); if (it != slots.end()) { slots.erase(it); // 從哈希表中移除槽 } } // 觸發(fā)信號(hào),調(diào)用所有已連接的槽 void emit(Args... args) const { for (const auto &pair : slots) { pair.second(args...); // 調(diào)用槽函數(shù) } } private: std::unordered_map<SlotID, SlotType> slots; // 存儲(chǔ)槽的哈希表 SlotID nextID = 0; // 用于生成唯一 ID 的計(jì)數(shù)器 }; #endif // SIGNAL_H
(2)連接槽的示例
我們使用 Signal
模板類連接多個(gè)槽,包括普通函數(shù)、Lambda 表達(dá)式和類成員函數(shù)。
#include "Signal.h" #include <iostream> #include <string> // 普通函數(shù)作為槽 void slot1(const std::string &message) { std::cout << "槽1 收到消息: " << message << std::endl; } // 普通函數(shù)作為槽 void slot2(const std::string &message) { std::cout << "槽2 收到消息: " << message << std::endl; } // 測(cè)試類,擁有自己的槽 class TestClass { public: // 成員函數(shù)作為槽 void classSlot(const std::string &message) { std::cout << "TestClass::classSlot 收到消息: " << message << std::endl; } };
(3)主程序示例
通過主程序,我們測(cè)試以下功能:
- 注冊(cè)普通函數(shù)、Lambda 表達(dá)式和成員函數(shù)到信號(hào)。
- 觸發(fā)信號(hào),調(diào)用所有槽。
- 動(dòng)態(tài)斷開某個(gè)槽,驗(yàn)證槽移除功能。
#include "Signal.h" #include <iostream> #include <string> int main() { // 創(chuàng)建一個(gè)信號(hào) Signal<std::string> signal; // 連接普通函數(shù)到信號(hào) auto id1 = signal.connect(slot1); auto id2 = signal.connect(slot2); // 創(chuàng)建一個(gè)類實(shí)例,并連接成員函數(shù)到信號(hào) TestClass obj; auto id3 = signal.connect([&obj](const std::string &message) { obj.classSlot(message); }); // 第一次觸發(fā)信號(hào),所有槽都會(huì)被調(diào)用 std::cout << "第一次觸發(fā)信號(hào):" << std::endl; signal.emit("你好,信號(hào)與槽!"); // 從信號(hào)中斷開槽1 std::cout << "\n斷開槽1后,第二次觸發(fā)信號(hào):" << std::endl; signal.disconnect(id1); // 第二次觸發(fā)信號(hào),僅槽2和成員函數(shù)槽會(huì)被調(diào)用 signal.emit("這是第二條消息!"); return 0; }
4. 運(yùn)行結(jié)果
運(yùn)行程序后,輸出如下:
第一次觸發(fā)信號(hào):
槽1 收到消息: 你好,信號(hào)與槽!
槽2 收到消息: 你好,信號(hào)與槽!
TestClass::classSlot 收到消息: 你好,信號(hào)與槽!斷開槽1后,第二次觸發(fā)信號(hào):
槽2 收到消息: 這是第二條消息!
TestClass::classSlot 收到消息: 這是第二條消息!
5. 代碼解析
槽的管理
- 每個(gè)槽函數(shù)通過
connect
方法注冊(cè)到信號(hào),信號(hào)會(huì)為每個(gè)槽分配一個(gè)唯一標(biāo)識(shí)符(SlotID
)。 - 槽函數(shù)存儲(chǔ)在
std::unordered_map
中,鍵為SlotID
,值為槽函數(shù)。
- 每個(gè)槽函數(shù)通過
信號(hào)的觸發(fā)
- 調(diào)用
emit
方法時(shí),會(huì)遍歷所有注冊(cè)的槽,并依次調(diào)用它們。
- 調(diào)用
槽的動(dòng)態(tài)移除
- 通過槽的唯一標(biāo)識(shí)符(
SlotID
),調(diào)用disconnect
方法,可以從信號(hào)中移除指定的槽。
- 通過槽的唯一標(biāo)識(shí)符(
支持多種類型的槽
- 使用
std::function
存儲(chǔ)槽,可以輕松支持普通函數(shù)、Lambda 表達(dá)式和類成員函數(shù)。
- 使用
6. 特點(diǎn)與優(yōu)點(diǎn)
優(yōu)點(diǎn)
- 模塊化設(shè)計(jì):
Signal
類實(shí)現(xiàn)信號(hào)的管理與觸發(fā),獨(dú)立、易用。
- 支持多樣化槽:
- 既支持普通函數(shù),又支持成員函數(shù)和 Lambda 表達(dá)式。
- 高性能:
- 使用
std::unordered_map
存儲(chǔ)槽,添加、移除和觸發(fā)的時(shí)間復(fù)雜度為 O(1)。
- 使用
特點(diǎn)
- 輕量級(jí)實(shí)現(xiàn): 僅依賴 C++ 標(biāo)準(zhǔn)庫,無需額外框架。
- 模板化設(shè)計(jì): 可以適配任意參數(shù)類型的信號(hào)與槽。
7. 應(yīng)用場(chǎng)景
- 事件驅(qū)動(dòng)開發(fā):
- 如 GUI 按鈕點(diǎn)擊、窗口關(guān)閉事件等場(chǎng)景。
- 解耦模塊:
- 觀察者模式中,用信號(hào)與槽代替觀察者通知機(jī)制。
- 回調(diào)機(jī)制:
- 替代傳統(tǒng)的回調(diào)函數(shù)方式,提供更靈活的信號(hào)與槽功能。
8. 總結(jié)
通過本文,我們實(shí)現(xiàn)了一個(gè)輕量級(jí)、功能完善的信號(hào)與槽系統(tǒng)。它借鑒了 Qt 的設(shè)計(jì)思想,但更加輕量化和靈活。這個(gè)設(shè)計(jì)可以輕松應(yīng)用于任意純 C++ 項(xiàng)目,特別適合事件驅(qū)動(dòng)和解耦通信的場(chǎng)景。如果需要擴(kuò)展到多線程環(huán)境,可以在此基礎(chǔ)上加入線程安全機(jī)制,如 std::mutex
。
你可以將此代碼作為基礎(chǔ),進(jìn)一步改造和優(yōu)化,打造符合你需求的高效信號(hào)與槽系統(tǒng)!
到此這篇關(guān)于使用C++實(shí)現(xiàn)類似Qt的信號(hào)與槽機(jī)制功能的文章就介紹到這了,更多相關(guān)C++實(shí)現(xiàn)信號(hào)與槽機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
matlab模擬退火算法單約束車間流水線調(diào)度解決實(shí)現(xiàn)及示例
這篇文章主要為大家介紹了matlab模擬退火算法求解單約束車間流水線調(diào)度的實(shí)現(xiàn)及示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-02-02C++使用WideCharToMultiByte函數(shù)生成UTF-8編碼文件的方法
用來映射Unicode字符串的WideCharToMultiByte函數(shù)經(jīng)常被用來進(jìn)行UTF-8編碼的轉(zhuǎn)換,以下我們將看到C++使用WideCharToMultiByte函數(shù)生成UTF-8編碼文件的方法,首先先來對(duì)WideCharToMultiByte作一個(gè)詳細(xì)的了解:2016-06-06利用C++開發(fā)一個(gè)protobuf動(dòng)態(tài)解析工具
數(shù)據(jù)庫中存儲(chǔ)的protobuf序列化的內(nèi)容,有時(shí)候查問題想直接解析查看內(nèi)容。很多編碼在網(wǎng)上很容易找到編解碼工具,但protobuf沒有找到編解碼工具,可能這樣的需求比較少吧,那就自己用C++實(shí)現(xiàn)一個(gè),感興趣的可以了解一下2023-01-01Visual C++ 6.0實(shí)現(xiàn)域名解析為IP的示例代碼
本文主要介紹了在Windows環(huán)境下,使用Visual C++ 6.0(VC6)編譯器,通過Winsock庫調(diào)用DNS服務(wù)完成域名到IP地址的轉(zhuǎn)換,具有一定的參考價(jià)值,感興趣的可以了解一下2025-03-03