槽函数的调用是一个多步骤的过程:
1. 连接(Connect)
首先,通过使用QObject::connect()
函数来建立信号和槽之间的连接。
QObject::connect(sender, SIGNAL(signalName(args)), receiver, SLOT(slotName(args)));
这里的sender
和receiver
是QObject派生的对象,而signalName
和slotName
则分别是信号和槽的名称。
2. 元对象信息
当moc
(Meta-Object Compiler)编译源代码时,它会为每个用Q_OBJECT
宏标记的类生成元对象信息。这些信息包括信号和槽的函数签名,以及它们在类的成员函数列表中的索引。
3. 发射信号(Emit Signal)
信号在某个特定事件发生时发射:
emit signalName(args);
4. 查找连接列表
当一个信号被发射,Qt元对象系统会查找所有连接到这个信号的槽。这些信息通常存储在一个内部数据结构中,该结构能够快速地找到与给定信号连接的所有槽。
5. 参数传递与转换
在调用槽函数之前,信号的参数需要与槽函数的参数进行匹配。如果槽函数的参数数量少于信号,那么多余的信号参数会被忽略。
6. 调用槽函数
-
直接连接(
Qt::DirectConnection
): 如果连接类型是直接连接,则槽函数将直接(在相同的线程中)被调用,就像是一个普通的C++函数调用。 -
队列连接(
Qt::QueuedConnection
): 如果连接类型是队列连接,槽函数的调用将被排入事件队列,并在稍后的事件循环中被处理。这通常会在接收者所在的线程的事件循环中完成。
7. 多对多关系
一个信号可以连接到多个槽,一个槽也可以与多个信号连接。在这种情况下,每次发射信号时,所有连接的槽都会被按照它们连接的顺序依次调用。
8. 断开连接(Disconnect)
可以使用QObject::disconnect()
函数来断开已有的连接。
code:
#include <iostream>
#include <map>
#include <vector>
#include <functional>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
enum ConnectionType {
AutoConnection,
DirectConnection,
QueuedConnection
};
enum EventType {
WorkDone,
WorkStarted
};
template<typename... Args>
class Signal {
public:
using Slot = std::function<void(Args...)>;
void connect(EventType event, Slot slot, ConnectionType type = AutoConnection);
void emit(EventType event, Args... args);
void processEvents();
private:
struct SlotWrapper {
Slot slot;
std::thread::id tid;
ConnectionType type;
SlotWrapper(Slot s, std::thread::id id, ConnectionType t) : slot(s), tid(id), type(t) {}
};
std::map<EventType, std::vector<SlotWrapper>> slots_;
std::mutex mutex_;
std::queue<std::function<void()>> taskQueue_;
std::mutex queueMutex_;
std::condition_variable cv_;
bool hasPendingEvents_ = false;
ConnectionType determineEffectiveConnectionType(const SlotWrapper& wrapper);
void enqueueTask(std::function<void()> task);
};
template<typename... Args>
ConnectionType Signal<Args...>::determineEffectiveConnectionType(const SlotWrapper& wrapper) {
if (wrapper.type == AutoConnection) {
return (wrapper.tid == std::this_thread::get_id()) ? DirectConnection : QueuedConnection;
}
return wrapper.type;
}
template<typename... Args>
void Signal<Args...>::connect(EventType event, Slot slot, ConnectionType type) {
std::lock_guard<std::mutex> lock(mutex_);
slots_[event].emplace_back(slot, std::this_thread::get_id(), type);
}
template<typename... Args>
void Signal<Args...>::emit(EventType event, Args... args) {
std::lock_guard<std::mutex> lock(mutex_);
for (const auto& wrapper : slots_[event]) {
ConnectionType effectiveType = determineEffectiveConnectionType(wrapper);
if (effectiveType == DirectConnection) {
wrapper.slot(args...);
} else if (effectiveType == QueuedConnection) {
enqueueTask([=]() { wrapper.slot(args...); });
}
}
}
template<typename... Args>
void Signal<Args...>::processEvents() {
std::unique_lock<std::mutex> lock(queueMutex_);
cv_.wait(lock, [&](){ return hasPendingEvents_; });
while (!taskQueue_.empty()) {
auto task = taskQueue_.front();
taskQueue_.pop();
task();
}
hasPendingEvents_ = false;
}
template<typename... Args>
void Signal<Args...>::enqueueTask(std::function<void()> task) {
taskQueue_.push(task);
hasPendingEvents_ = true;
cv_.notify_one();
}
class Worker {
public:
Signal<> workDone;
void doWork() {
workDone.emit(WorkDone);
}
};
/* compile : g++ -std=c++11 -lpthread example.cp */
int main() {
Signal<> signal;
signal.connect(WorkDone, []() {
std::cout << "Slot executed in thread 1: " << std::this_thread::get_id() << std::endl;
});
signal.emit(WorkDone);
// 模拟在其他线程connect和emit
std::thread workerThread([&]() {
signal.connect(WorkDone, []() {
std::cout << "Slot executed in thread 3: " << std::this_thread::get_id() << std::endl;
});
signal.emit(WorkDone);
});
std::this_thread::sleep_for(std::chrono::seconds(1));
// 主线程循环
while (true) {
signal.processEvents();
if (!workerThread.joinable()) break;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
workerThread.join();
return 0;
}