纯C++代码理解Qt中的信号和槽函数

槽函数的调用是一个多步骤的过程:

1. 连接(Connect)

首先,通过使用QObject::connect()函数来建立信号和槽之间的连接。

QObject::connect(sender, SIGNAL(signalName(args)), receiver, SLOT(slotName(args)));

这里的senderreceiver是QObject派生的对象,而signalNameslotName则分别是信号和槽的名称。

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;
}
posted on 2023-09-12 17:57  杜jn  阅读(94)  评论(0编辑  收藏  举报