sigslot库源码分析

简介:

最近一直在忙毕设的事情,博客都快被遗忘了。最近正好在研究sigslot库,索性晚上写点源码分析的水文充充数。

言归正传,sigslot是一个用标准C++语法实现的信号与槽机制的函数库,类型和线程安全。提到信号与槽机制,恐怕最容易想到的就是大名鼎鼎的Qt所支持的对象之间通信的模式吧。不过这里的信号与槽虽然在概念上等价与Qt所实现的信号与槽,但是采用的仅仅是标准C++语法,不像Qt采用了扩展C++语言的方式(Qt需要使用moc工具对代码预处理之后,才能由标准的C++编译器进行编译)。

众所周知,C++是一门特性众多的语言,其支持多种编程范式。虽然C++在一定程度上支持OOP编程,但是C++这种“静态消息机制”的语言一直没有实现对象级别的delegate机制,而C++之父Bjarne主张的“库扩展胜于语言扩展”的做法使得各种解决方案层出不穷。除了信号与槽机制,C++11正式加入的std:bind/std::function组合也提供了优秀的解决方案。这里所说的信号与槽机制也是一种对象间通信的机制,具体的讨论也可以看看sigslot相关介绍中的内容。

sigslot主页: http://sigslot.sourceforge.net

sigslot文档: http://sigslot.sourceforge.net/sigslot.pdf

sigslot库的用法文档中已然很明了了,所以在这里就不赘述了。接下来我们看看这个库的实现。源码分析的方法有很多种,具体到库代码的分析的方法,我喜欢的是先研究库的功能,直到能写出一个demo程序为止。研究一个库的前提是你得会用它,熟悉它的接口。读完文档,很容易就写出了下面的测试代码:


 #include <iostream>
#include "sigslot.h"

using namespace sigslot;

class Switch
{
public:
    signal0<> clicked;
};

class Light : public has_slots<>
{
public:
    void turn_on()
    {
        std::cout << "Turn on ~" << std::endl;
    }
};

int main(int argc, char *argv[])
{
    Light lit1;
    Switch swh;

    swh.clicked.connect(&lit1, &Light::turn_on);
    swh.clicked.emit();

    return 0;
}
 

使用方法很简单。从这里我们就能看出来,这个库无非就是在信号那一端保存了这个信号所绑定的函数指针,在槽函数这一端保存了其绑定的信号而已。接下来的问题实际上就是采用合理的数据结构来处理问题了。

sigslot库简单到只有一个头文件sigslot.h,打开后洋洋洒洒几千行代码,其实仔细看看绝大多数代码都是为了适应参数数量不同的成员函数指针的定义,为其扩展的模版代码。从定义上看,这个库支持0~8个参数的成员函数绑定。在纸上画一下类的继承关系,很容易就得到了如下的函数继承图(IDE有相关的工具也可以拿来用~):

从这个图上看,其实代码关系已经很清晰了。实现了槽函数的类需要继承has_slots类。而has_slots类拥有一个std::set<_signal_base<mt_policy>*>类型的容器(所有的mt_policy其实是库定义的三种锁策略而已[单线程无锁、多线程共享全局锁、多线程局部锁])。所有的_signal_base[0-8]的类持有各自的std::list<_connection_base[0-8]<mt_policy>>的list容器,而_connection_base[0-8]则分别封装了0~8个参数的成员函数的指针。

这里的重复代码是很多的,作为分析的话完全可以每中类代码只留下一个,这样所有的代码就精简到只有6个类了(反正别的也只是为了适应参数个数写的模版罢了,代码除了参数个数外都是一样的)。

至于前面说到的锁,其实也只是因为C++ STL库中的容器本身不是线程安全的,需要在外部加锁。锁的实现很平常,另外用C++ RAII手法封装的lock_block类也是常见的用法。唯一需要注意的是,这个库在使用了信号与槽的用户类发生了拷贝构造时,其信号与槽的绑定关系也会被拷贝,所以代码中的类都自行编写了相关的拷贝构造函数。这里稍微解释下,如果A类的a对象的x信号和B类的b1对象的y函数绑定,然后用b1初始化构造b2(即 B b2(b1))。这时候,A类的a对象的x信号也会绑定到b2对象的y函数。这个特性我感觉有点莫名其妙,而且使得代码复杂了不少(我觉得没必要这么设计,用户需要这个特性的话,自己再调用一次绑定函数就是了)。

知晓了基本的原理之后,看代码就很容易了。比如在拥有信号和拥有槽函数的对象析构时,会自动的取消掉之前的绑定,代码很清晰易读的。下面是我自己根据sigslot的原理,简化出来的代码,大家可以先看看然后去读sigslot的源码会简单很多。

代码如下(去掉了复制拷贝和锁相关的代码,线程不安全,仅供参考):


#ifndef _SIGNAL_SLOT_H__
#define _SIGNAL_SLOT_H__

#include <set>
#include <list>

namespace signalslot {

// 前置声明 has_slots 类
class has_slots;

// 连接管理信息虚基类
class _connection_base
{
public:
    virtual has_slots *getdest() const = 0;
    virtual void emit() = 0;
};

// 信号的虚基类
class _signal_base
{
public:
    virtual void slot_disconnect(has_slots *pslot) = 0;
};

// has_slots 类
// 所有实现了槽函数的用户代码必须继承自该类
class has_slots
{
    typedef std::set<_signal_base *> sender_set;
    typedef sender_set::const_iterator const_iterator;

private:
    sender_set senders_;

public:
    has_slots()
    { }

    virtual ~has_slots()
    {
        // 含有信号与槽的对象析构时,断开所有连接的信号与槽
        disconnect_all();
    }

    // 当含有槽函数的对象被拷贝时只对槽数据进行初始化
    // 不拷贝信号和槽的绑定关系
    has_slots(const has_slots &)
    { }

    // 断开所有连接的信号与槽
    void disconnect_all()
    {
        const_iterator it = senders_.begin();
        const_iterator end = senders_.end();
        while (it != end) {
            (*it)->slot_disconnect(this);
            ++it;
        }
        senders_.clear();
    }

    // 仅供信号相关的类回调,不允许用户调用
    void signal_connect(_signal_base *sender)
    {
        senders_.insert(sender);
    }

    // 仅供信号相关的类回调,不允许用户调用
    void signal_disconnect(_signal_base *sender)
    {
        senders_.erase(sender);
    }

};

// 封装了一个类和其成员函数的指针
template <typename dest_type>
class _connection : public _connection_base
{
private:
    // 泛型类指针
    dest_type *pobject_;

    // 泛型成员函数指针
    void (dest_type::*pmemfun_)();

public:
    _connection(dest_type *pobject, void (dest_type::*pmemfun)())
    {
        pobject_ = pobject;
        pmemfun_ = pmemfun;
    }

    virtual has_slots *getdest() const
    {
        return pobject_;
    }

    virtual void emit()
    {
        (pobject_->*pmemfun_)();
    }
};

class signal : public _signal_base
{
    typedef std::list<_connection_base *> connections_list;
    typedef connections_list::const_iterator const_iterator;

protected:
    connections_list connected_slots_;

public:
    signal()
    { }

    // 信号对象销毁时自动断开所有槽函数的连接
    ~signal()
    {
        disconnect_all();
    }

    // 当含有信号的对象被拷贝时只对信号进行初始化
    // 不拷贝信号和槽的绑定关系
    signal(const signal &)
    { }

    void disconnect_all()
    {
        const_iterator it = connected_slots_.begin();
        const_iterator end = connected_slots_.end();
        while (it != end) {
            (*it)->getdest()->signal_disconnect(this);
            delete *it;
            ++it;
        }
        connected_slots_.clear();
    }

    template <typename desttype>
    void connect(desttype *pclass, void (desttype::*pmemfun)())
    {
        _connection_base *conn = new _connection<desttype>(pclass, pmemfun);
        connected_slots_.push_back(conn);

        pclass->signal_connect(this);
    }

    void disconnect(has_slots *pclass)
    {
        const_iterator it = connected_slots_.begin();
        const_iterator end = connected_slots_.end();
        while (it != end) {
            if ((*it)->getdest() == pclass) {
                delete *it;
                connected_slots_.erase(it);
                pclass->signal_disconnect(this);
                return;
            }
            ++it;
        }
    }

    void emit()
    {
        const_iterator it = connected_slots_.begin();
        const_iterator end = connected_slots_.end();
        while (it != end) {
            (*it)->emit();
            ++it;
        }
    }

    void operator()()
    {
        emit();
    }

    void slot_disconnect(has_slots *pslot)
    {
        const_iterator it = connected_slots_.begin();
        const_iterator end = connected_slots_.end();
        while (it != end) {
            if ((*it)->getdest() == pslot) {
                connected_slots_.erase(it);
            }
            ++it;
        }
    }

};

} // namespace signalslot

#endif // _SIGNAL_SLOT_H__
 


目录
相关文章
|
编解码 文字识别 测试技术
3000 字带你了解Claude3 视觉能力,OCR, 菜单识别统统能搞定!
五大任务,带你了解Claude3的视觉能力有多强 2024 年 3 月 4 日,Anthropic 震撼发布了全新的多模态模型——Claude 3。据该公司介绍,无论是语言处理还是视觉识别任务,Claude 3 都展现出了超越同类竞争产品(例如配备视觉功能的 GPT-4)的卓越性能。
486 0
wireshark解析rtp协议,流媒体中的AMR/H263/H264包的方法
抓到完整的流媒体包之后,用wireshark打开,其中的包可能不会自动映射成RTP+AMR/H263/H264的包,做如下修改操作即可:1.  把UDP 包解析成RTP/RTCP包。选中UDP包,右键,选择Decode As,选RTP2.  把RTP Payload映射成实际的媒体格式。
3494 0
搭建内网的NTP时间服务器
NTP时间服务器 标签: linux 笔者Q:972581034 交流群:605799367。有任何疑问可与笔者或加群交流 1.简介 NTP(Network Time Protocol,网络时间协议)是用来使网络中的各个计算机时间同步的一种协议。
2045 0
|
5月前
|
人工智能 自然语言处理 安全
魔搭社区模型速递(7.12-7.19)
🙋魔搭ModelScope本期社区进展:3072个模型,193个数据集,121个创新应用:Qwen-TTS-Demo 📄 8 篇内容:
349 0
|
4月前
|
JSON 人工智能 JavaScript
cursor 如何调用 MCP server
本文介绍了如何在 Cursor 中配置并调用 MCP Server,以实现天气信息查询功能。内容涵盖 MCP 配置步骤、JSON 文件设置、MCP Server 的调用方法及结果展示,帮助开发者快速集成外部服务。
|
6月前
|
机器学习/深度学习 人工智能 算法
AI Agent驱动下的金融智能化:技术实现与行业影响
本文探讨了AI Agent在金融领域的技术实现与行业影响,涵盖智能投顾、风险控制、市场分析及反欺诈等应用场景。通过感知、知识管理、决策和行动四大模块,AI Agent推动金融从自动化迈向智能化。文中以Python代码展示了基于Q-learning的简易金融AI Agent构建过程,并分析其带来的效率革命、决策智能化、普惠金融和风控提升等变革。同时,文章也指出了数据安全、监管合规及多Agent协作等挑战,展望了结合大模型与增强学习的未来趋势。最终,AI Agent有望成为金融决策中枢,实现“智管钱”的飞跃。
AI Agent驱动下的金融智能化:技术实现与行业影响
|
10月前
|
人工智能 文字识别 自然语言处理
保单AI识别技术及代码示例解析
车险保单包含基础信息、车辆信息、人员信息、保险条款及特别约定等关键内容。AI识别技术通过OCR、文档结构化解析和数据校验,实现对保单信息的精准提取。然而,版式多样性、信息复杂性、图像质量和法律术语解析是主要挑战。Python代码示例展示了如何使用PaddleOCR进行保单信息抽取,并提出了定制化训练、版式分析等优化方向。典型应用场景包括智能录入、快速核保、理赔自动化等。未来将向多模态融合、自适应学习和跨区域兼容性发展。
|
编解码 监控 网络协议
如何用魔法般的步骤实现RTSP推送H.264与H.265(HEVC),打造震撼视听盛宴,让每一帧都充满魔力!
【9月更文挑战第3天】实现RTSP流媒体服务推送H.264和H.265编码视频是现代视频监控及直播平台的关键技术。本文详细介绍环境搭建、编码配置及服务器与客户端实现方法。首先,通过FFmpeg捕获视频并编码成RTSP流,接着使用VLC等工具接收播放。此外,还提供了C++示例代码,演示如何利用libv4l2和FFmpeg自定义服务器端实现。希望本文能帮助读者成功搭建RTSP视频流系统。
2373 1
|
机器学习/深度学习 Python
【10月更文挑战第5天】「Mac上学Python 6」入门篇6 - 安装与使用Anaconda
本篇将详细介绍如何在Mac系统上安装和配置Anaconda,如何创建虚拟环境,并学习如何使用 `pip` 和 `conda` 管理Python包,直到成功运行第一个Python程序。通过本篇,您将学会如何高效地使用Anaconda创建和管理虚拟环境,并使用Python开发。
445 4
【10月更文挑战第5天】「Mac上学Python 6」入门篇6 - 安装与使用Anaconda