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__
 
时间: 2024-10-03 15:14:46

sigslot库源码分析的相关文章

cJSON库源码分析

cJSON是一个超轻巧,携带方便,单文件,简单的可以作为ANSI-C标准的Json格式解析库. 那什么是Json格式?这里照搬度娘百科的说法: Json(JavaScript Object Notation) 是一种轻量级的数据交换格式.它基于JavaScript(Standard ECMA-262 3rd Edition – December 1999)的一个子集.JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScri

从源码分析Android的Glide库的图片加载流程及特点_Android

0.基础知识Glide中有一部分单词,我不知道用什么中文可以确切的表达出含义,用英文单词可能在行文中更加合适,还有一些词在Glide中有特别的含义,我理解的可能也不深入,这里先记录一下. (1)View: 一般情况下,指Android中的View及其子类控件(包括自定义的),尤其指ImageView.这些控件可在上面绘制Drawable (2)Target: Glide中重要的概念,目标.它即可以指封装了一个View的Target(ViewTarget),也可以不包含View(SimpleTar

Jquery源码分析---概述

jQuery是一个非常优秀的JS库,与Prototype,YUI,Mootools等众多的Js类库 相比,它剑走偏锋,从web开发实用的角度出发,抛除了其它Lib中一些不实用的 东西,为开发者提供了短小精悍的类库.其短小精悍,使用简单方便,性能高效 ,能极大地提高开发效率,是开发web应用的最佳的辅助工具之一.因此大部分 开发者在抛弃Prototype而选择Jquery来进行web开发. 一些开发人员在使用jquery时,由于仅仅只知道Jquery文档中的使用方法, 不明白Jquery的运行原理

Jquery源码分析---导言

jQuery是一个非常优秀的JS库,与Prototype,YUI,Mootools等众多的Js类库 相比,它剑走偏锋,从web开发的实用角度出发,抛除了其它Lib中一些中看但不 实用的东西,为开发者提供了优美短小而精悍的类库.其使用简单,文档丰富, 而且性能高效,能极大地提高web系统的开发效率.因此可以说是web应用开发中 最佳的Js辅助类库之一.大部分开发者正在抛弃Prototype,而选择Jquery做为 他们进行web开发的JS库. 如是开发人员仅仅只知道文档中的简单的使用 方法,却不明

OkHttp 3.7源码分析(一)——整体架构

OkHttp3.7源码分析文章列表如下: OkHttp源码分析--整体架构 OkHttp源码分析--拦截器 OkHttp源码分析--任务队列 OkHttp源码分析--缓存策略 OkHttp源码分析--多路复用 OkHttp是一个处理网络请求的开源项目,是Android端最火热的轻量级框架,由移动支付Square公司贡献用于替代HttpUrlConnection和Apache HttpClient.随着OkHttp的不断成熟,越来越多的Android开发者使用OkHttp作为网络框架. 之所以可以

深入浅析knockout源码分析之订阅_javascript技巧

Knockout.js是什么? Knockout是一款很优秀的JavaScript库,它可以帮助你仅使用一个清晰整洁的底层数据模型(data model)即可创建一个富文本且具有良好的显示和编辑功能的用户界面.任何时候你的局部UI内容需要自动更新(比如:依赖于用户行为的改变或者外部的数据源发生变化),KO都可以很简单的帮你实现,并且非常易于维护. 一.主类关系图 二.类职责 2.1.observable(普通监控对象类) observable(他其是一个function)的内部实现: 1.首先声

《深入理解SPARK:核心思想与源码分析》一书正式出版上市

自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的<深入理解Spark:核心思想与源码分析>一书现在已经正式出版上市,目前亚马逊.京东.当当.天猫等网站均有销售,欢迎感兴趣的同学购买.我开始研究源码时的Spark版本是1.2.0,经过7个多月的研究和出版社近4个月的流程,Spark自身的版本迭代也很快,如今最新已经是1.6.0.目前市面上另外2本源码研究的Spark书籍的版本分别是0.9.0版本和1.2.0版本,看来这些书的作者都与我一样,遇到了这种问题.由于研究和

深入理解Spark:核心思想与源码分析

大数据技术丛书 深入理解Spark:核心思想与源码分析 耿嘉安 著 图书在版编目(CIP)数据 深入理解Spark:核心思想与源码分析/耿嘉安著. -北京:机械工业出版社,2015.12 (大数据技术丛书) ISBN 978-7-111-52234-8 I. 深- II.耿- III.数据处理软件 IV. TP274 中国版本图书馆CIP数据核字(2015)第280808号 深入理解Spark:核心思想与源码分析 出版发行:机械工业出版社(北京市西城区百万庄大街22号 邮政编码:100037)

Angularjs 源码分析1

AngularJS简介 angularjs 是google出品的一款MVVM前端框架,包含一个精简的类jquery库,创新的开发了以指令的方式来组件化前端开发,可以去它的官网看看,请戳这里 再贴上一个本文源码分析对应的angularjs源码合并版本1.2.4,精简版的,除掉了所有的注释, 请戳这里 从启动开始说起 定位到4939行,这里是angularjs开始执行初始化的地方,见代码 bindJQuery(), publishExternalAPI(angular), jqLite(docume