当前位置: 首页 > news >正文

C++信号与槽机制自实现

1. 信号与槽的概念

信号与槽是一种用于实现事件驱动编程的设计模式,主要用于对象之间的解耦通信。这种模式源于Qt框架,但其概念可以应用于任何C++程序。在这种模式中:

  • 信号(Signal):代表一种事件的发生。当特定条件满足时,信号会被"发射"(emit),通知所有监听这个信号的对象。
  • 槽(Slot):是一个响应特定信号的函数。当信号被发射时,所有连接到该信号的槽都会被自动调用。

举个例子:当用户在界面上点击一个按钮时,按钮发出一个"clicked"信号,连接的槽函数则执行相应的操作,如更新界面或处理数据。

这种机制的优势在于:

  • 低耦合:发送信号的对象不需要知道接收信号的对象是谁
  • 类型安全:通过模板实现类型检查
  • 灵活性:可以动态地连接和断开信号与槽

在这里插入图片描述

2. 模块设计

2.1 Signal 模板类

Signal 类是实现信号与槽机制的核心,负责管理信号的发射和槽的连接。它需要具备以下功能:

  • connect:连接一个槽函数到信号,返回唯一标识符
  • disconnect:通过唯一ID移除指定的槽函数
  • emit:发射信号,调用所有已注册的槽

下面是 Signal 类的实现示例:

#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)

以下是连接各类型槽的示例代码:

#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 主程序示例

在主程序中,我们将测试各种功能:

#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 自动断开连接

为了防止在对象销毁后仍然调用其成员函数,我们可以实现自动断开连接的机制:

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_;
};

3.2 信号阻塞

…详情请参照古月居

相关文章:

  • 桂林旅游网站制作品牌策划方案范文
  • 建设监理工程师网站深圳seo关键词优化
  • 网站如何做h5动态页面设计uc搜索引擎入口
  • 网站的功能有哪些seo关键词快速获得排名
  • 南京网站优化平台全球搜索大全
  • 做网站哪家公司最好天津放心站内优化seo
  • win10 笔记本电脑安装 pytorch+cuda+gpu 大模型开发环境过程记录
  • git push
  • 蓝桥杯单片机频率
  • YOLO环境搭建,win11+wsl2+ubuntu24+cuda12.6+idea
  • C# Winform 入门(9)之如何封装并调用dll
  • 如何提高rabbitmq消费效率
  • C#中为自定义控件设置工具箱图标
  • OpenRouter - 创建 API Keys、OpenAI 调用 以及在Cline 中配置使用
  • 连续数据离散化与逆离散化策略
  • 学习笔记—C++—入门基础()
  • Qt之QHostInfo
  • 嵌入式AI的本地化部署的好处
  • 【51单片机】2-6【I/O口】【电动车简易防盗报警器实现】
  • 蓝桥云客--浓缩咖啡液
  • 前端精度计算:Decimal.js 基本用法与详解
  • VUE3组件综合应用(日历组件)
  • 8.5/Q1,Charls最新文章解读
  • stc8g1k08a定时读取内部1.2v电压值 vcc电压发送到串口1
  • pycharm 有智能提示,但是没法自动导包,也就是alt+enter无效果
  • JavaScript基础--03-变量的数据类型:基本数据类型和引用数据类型