1. 信号与槽的概念
信号与槽是一种用于实现事件驱动编程的设计模式,主要用于对象之间的解耦通信。这种模式源于Qt框架,但其概念可以应用于任何C++程序。在这种模式中:
- 信号(Signal):代表一种事件的发生。当特定条件满足时,信号会被"发射"(emit),通知所有监听这个信号的对象。
- 槽(Slot):是一个响应特定信号的函数。当信号被发射时,所有连接到该信号的槽都会被自动调用。
举个例子:当用户在界面上点击一个按钮时,按钮发出一个"clicked"信号,连接的槽函数则执行相应的操作,如更新界面或处理数据。
这种机制的优势在于:
- 低耦合:发送信号的对象不需要知道接收信号的对象是谁
- 类型安全:通过模板实现类型检查
- 灵活性:可以动态地连接和断开信号与槽

2. 模块设计
2.1 Signal 模板类
Signal
类是实现信号与槽机制的核心,负责管理信号的发射和槽的连接。它需要具备以下功能:
connect
:连接一个槽函数到信号,返回唯一标识符disconnect
:通过唯一ID移除指定的槽函数emit
:发射信号,调用所有已注册的槽
下面是 Signal
类的实现示例:
cpp
#ifndef SIGNAL_H
#define SIGNAL_H
#include <unordered_map>
#include <functional>
#include <iostream>
template <typename... Args>
class Signal {
public:
using SlotType = std::function<void(Args...)>;
using SlotID = int;
SlotID connect(SlotType slot) {
SlotID id = nextID++;
slots[id] = slot;
return id;
}
void disconnect(SlotID id) {
slots.erase(id);
}
void emit(Args... args) const {
for (const auto& pair : slots) {
pair.second(args...);
}
}
private:
std::unordered_map<SlotID, SlotType> slots;
SlotID nextID = 0;
};
#endif // SIGNAL_H
这个实现使用了C++11的可变参数模板,允许信号携带任意数量和类型的参数。每个槽通过std::function
包装,提供了极大的灵活性。
2.2 连接槽的示例
槽函数可以是多种形式,包括:
- 普通函数
- Lambda表达式
- 类成员函数
- 函数对象(functor)
以下是连接各类型槽的示例代码:
cpp
#include "Signal.h"
#include <iostream>
#include <string>
// 普通函数作为槽
void onMessageReceived(const std::string &message) {
std::cout << "Received message: " << message << std::endl;
}
// 带有多个参数的槽函数
void onStatusChange(const std::string &component, bool status) {
std::cout << "Component " << component << " status: "
<< (status ? "online" : "offline") << std::endl;
}
// 使用类成员函数作为槽
class Example {
public:
void onExampleEvent(const std::string &message) {
std::cout << "Example::onExampleEvent: " << message << std::endl;
}
void onValueChanged(int newValue) {
value = newValue;
std::cout << "Value updated to: " << value << std::endl;
}
private:
int value = 0;
};
// 函数对象作为槽
class MessageLogger {
public:
void operator()(const std::string &message) {
std::cout << "[Logger] " << message << std::endl;
}
};
2.3 主程序示例
在主程序中,我们将测试各种功能:
cpp
#include "Signal.h"
#include <iostream>
#include <string>
int main() {
// 创建不同类型的信号
Signal<std::string> messageSignal;
Signal<std::string, bool> statusSignal;
Signal<int> valueSignal;
// 创建用于测试的对象
Example exampleObj;
MessageLogger logger;
// 连接普通函数
auto id1 = messageSignal.connect(onMessageReceived);
// 连接成员函数
auto id2 = messageSignal.connect([&exampleObj](const std::string &message) {
exampleObj.onExampleEvent(message);
});
// 连接函数对象
auto id3 = messageSignal.connect(logger);
// 连接Lambda表达式
auto id4 = messageSignal.connect([](const std::string &message) {
std::cout << "Lambda handler: " << message << std::endl;
});
// 连接多参数信号
statusSignal.connect(onStatusChange);
// 连接值变化信号
valueSignal.connect([&exampleObj](int value) {
exampleObj.onValueChanged(value);
});
// 发射信号,所有槽都会被调用
std::cout << "=== Emitting messageSignal ===" << std::endl;
messageSignal.emit("Hello, World!");
// 断开某个槽
std::cout << "\n=== Disconnecting first slot and emitting again ===" << std::endl;
messageSignal.disconnect(id1);
messageSignal.emit("This message won't reach onMessageReceived");
// 测试多参数信号
std::cout << "\n=== Testing statusSignal ===" << std::endl;
statusSignal.emit("Database", true);
statusSignal.emit("Network", false);
// 测试值变化信号
std::cout << "\n=== Testing valueSignal ===" << std::endl;
valueSignal.emit(42);
valueSignal.emit(100);
return 0;
}
3. 扩展功能
基于上述基本实现,我们可以添加一些高级特性:
3.1 自动断开连接
为了防止在对象销毁后仍然调用其成员函数,我们可以实现自动断开连接的机制:
cpp
template <typename... Args>
class ScopedConnection {
public:
ScopedConnection(Signal<Args...>& signal, typename Signal<Args...>::SlotID id)
: signal_(&signal), id_(id), connected_(true) {}
// 移动构造函数
ScopedConnection(ScopedConnection&& other) noexcept
: signal_(other.signal_), id_(other.id_), connected_(other.connected_) {
other.connected_ = false;
}
// 禁止复制
ScopedConnection(const ScopedConnection&) = delete;
ScopedConnection& operator=(const ScopedConnection&) = delete;
~ScopedConnection() {
disconnect();
}
void disconnect() {
if (connected_ && signal_) {
signal_->disconnect(id_);
connected_ = false;
}
}
private:
Signal<Args...>* signal_;
typename Signal<Args...>::SlotID id_;
bool connected_;
};