QT信号和槽机制实现及源码阅读

2023-11-19

说明

QT的信号和槽采用观察者模式,Q_OBJECT是提供信号和槽的基础,使用过connect第五个参数的可知,槽可以在信号发出的线程处理,也可以加入任务队列进行处理。但是在此只写了在触发线程处理的代码

如下是实现的类QT信号和槽

/*************************************************************************
    > File Name: qt_connect.cc
    > Author: hsz
    > Brief:
    > Created Time: Wed 10 Aug 2022 09:08:14 AM CST
 ************************************************************************/

#include <iostream>
#include <vector>
#include <string>

template<typename Func> struct FunctionPointer { };

// 类的成员函数
template<class Obj, typename Ret, typename... Args>
struct FunctionPointer<Ret (Obj::*) (Args...)>
{
    typedef Obj Object;
    typedef Ret ReturnType;
    typedef Ret (Obj::*Function) (Args...);

    static void call(Function f, Obj *o, Args... args) {
        (o->*f)(std::forward<Args>(args)...);
    }
};

// 类的成员函数 const
template<class Obj, typename Ret, typename... Args>
struct FunctionPointer<Ret (Obj::*) (Args...) const>
{
    typedef Obj Object;
    typedef Ret ReturnType;
    typedef Ret (Obj::*Function) (Args...) const;

    static void call(Function f, Obj *o, Args... args) {
        (o->*f)(std::forward<Args>(args)...);
    }
};

// 普通函数
template<typename Ret, typename... Args>
struct FunctionPointer<Ret (*) (Args...)>
{
    typedef Ret ReturnType;
    typedef Ret (*Function) (Args...);

    static void call(Function f, Args... args) {
        f(std::forward<Args>(args)...);
    }
};


template<typename... Args>
class SlotBase
{
public:
    virtual ~SlotBase() = default;
    void exec(Args... args)
    {
        slotFunction(std::forward<Args>(args)...);
    }

protected:
    virtual void slotFunction(Args... args) = 0;
};

template<typename TRecver, typename SlotFunc, typename... Args>
class Slot : public SlotBase<Args...>
{
public:
    typedef FunctionPointer<SlotFunc> FuncType;

    Slot(TRecver *recver, SlotFunc func)
    {
        mRecver = recver;
        mFunc = func;
    }
    virtual ~Slot() {}

protected:
    virtual void slotFunction(Args... args) override
    {
        FuncType::call(mFunc, mRecver, std::forward<Args>(args)...);
    }

private:
    TRecver *mRecver;
    SlotFunc mFunc;
};

template<typename... Args>
class Signaler
{
public:
    Signaler() {}
    ~Signaler() {}

    template<typename TRecver, typename SlotFunc>
    void addSlot(TRecver *recver, SlotFunc func)
    {
        mSlotVec.push_back(new Slot<TRecver, SlotFunc, Args...>(recver, func));
    }

    void operator()(Args... args)
    {
        for (SlotBase<Args...> *it : mSlotVec)
        {
            it->exec(args...);
        }
    }

private:
    std::vector<SlotBase<Args...> *> mSlotVec;
};

class Recv1
{
public:
    void func(int num, std::string str)
    {
        printf("Recv1::func(%d, %s)\n", num, str.c_str());
    }
};

class Recv2
{
public:
    void func(int num, std::string str)
    {
        printf("Recv2::func(%d, %s)\n", num, str.c_str());
    }
};

class Send
{
public:
    Signaler<int, std::string> valueChanged;

public:
    void test(int num, std::string str)
    {
        printf("%p\n", str.c_str());
        valueChanged(num, str);
    }
};

#define connect(sender, signal, recver, slot) \
    (sender)->signal.addSlot(recver, slot);


int main(int argc, char **argv)
{
    Send send;
    Recv1 r1;
    Recv2 r2;

    connect(&send, valueChanged, &r1, &Recv1::func);
    connect(&send, valueChanged, &r2, &Recv2::func);

    send.test(10, std::string("Hello"));
    return 0;
}

运行结果
在这里插入图片描述

QT实现

QT的信号和槽需要moc进行处理,生成moc_xxx.cpp文件,里面存放的是一些meta相关的初始化函数实现及信号函数的实现。所以信号函数只需声明,而不需要自己实现。
QT的多参是通过模板实现的,如上的FunctionPointer就是QT中的源码,只不过我在此代码中进行了不必要的简化。
1、先来说说信号函数如何传递返回值及参数

// 从某一个moc文件截取的信号函数
int MainWindow::valueChange(uint16_t _t1, double _t2)
{
    int _t0{};
    void *_a[] = { 
    	const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t0))), 
    	const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))), 
    	const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t2))) };

    QMetaObject::activate(this, &staticMetaObject, 0, _a);
    return _t0;
}
/*
  如上便是一个信号函数的实现,可以看见其参数为uint16_t和double及int返回值
  其中_a数组保存的就是这三个参数的地址。staticMetaObject是Q_OBJECT宏声明的
  activate是调用槽函数
	
  _a数组内容:返回值(void时为nullptr), 参数1地址, 参数2地址, ...
  当无参无返回值时就是含有一个nullptr的数组
  
  由此可知,触发信号其实是调用的信号函数,至于emit宏,其本质是个空的,预处理后就没了,
  QT加上它是方便程序员看代码,看到emit就知道这是个信号,没有这个emit则会以为是某一处理函数
 */

2、如何调用到槽函数
不管是什么样的信号函数,其都会调用到如下activate

// qobject.cpp
void QMetaObject::activate(QObject *sender, int signalOffset, 
						   int local_signal_index, void **argv);

跳过一些条件判断来到具体执行函数
首先是对信号加锁,所以极短时间触发两次信号会导致第二次信号阻塞。
(这个锁并不是meta自带的,而是取自锁池,获取某一个锁还和sender地址相关)

// 没看懂为什么这么设计
// 一个大型的项目,其sender肯定超过131个,所以某两个sender势必会取到同一个锁,
// 当两个同时触发会导致某一个会阻塞住,这样是不是也就可以解释当在ui线程中处理一个耗时任务时,
// 在拖拽窗口会出现弹出无响应框
QMutexLocker locker(signalSlotLock(sender));


static QBasicMutex _q_ObjectMutexPool[131];
/**
 * \internal
 * mutex to be locked when accessing the connectionlists or the senders list
 */
static inline QMutex *signalSlotLock(const QObject *o)
{
    return static_cast<QMutex *>(&_q_ObjectMutexPool[
        uint(quintptr(o)) % sizeof(_q_ObjectMutexPool)/sizeof(QBasicMutex)]);
}

继续往下走可以看到声明了一个结构体,这个结构体的作用类似智能指针,管理inUse这个变量
QObjectConnectionListVector 这个类存储是一个object的所有QMetaObject::Connection对象(connect返回值就是QMetaObject::Connection),他是一个数组加链表结构,数组的索引是某一信号,链表是具体存储这一信号所connect的所有QMetaObject::Connection对象,如下
在这里插入图片描述

信号的索引 = signalOffset + local_signal_index;

struct ConnectionListsRef {
        QObjectConnectionListVector *connectionLists;
        ConnectionListsRef(QObjectConnectionListVector *connectionLists) : connectionLists(connectionLists)
        {
            if (connectionLists)
                ++connectionLists->inUse;
        }
        ~ConnectionListsRef()
        {
            if (!connectionLists)
                return;

            --connectionLists->inUse;
            Q_ASSERT(connectionLists->inUse >= 0);
            if (connectionLists->orphaned) {
                if (!connectionLists->inUse)
                    delete connectionLists;
            }
        }

        QObjectConnectionListVector *operator->() const { return connectionLists; }
    };

知道了信号的索引位置就可以取到这个connection链表
接下来就是遍历链表,执行槽函数(由于代码过多,只放一下流程代码)

int signal_index = signalOffset + local_signal_index;
ConnectionListsRef connectionLists = sender->d_func()->connectionLists;
list = &connectionLists->at(signal_index);

do {
	QObjectPrivate::Connection *c = list->first;
	if (!c) continue;
	QObjectPrivate::Connection *last = list->last;
	
	do {
		QObject * const receiver = c->receiver;
		// 这个就是判断connect第五参数的类型,是否在本线程执行槽函数还是加入线程队列
		if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
                || (c->connectionType == Qt::QueuedConnection)) {
            queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker);
            continue;
        }
        if (c->isSlotObject) {
            c->slotObj->ref();
            QScopedPointer<QtPrivate::QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj);
            locker.unlock();

            {
                Q_TRACE_SCOPE(QMetaObject_activate_slot_functor, obj.data());
                // obj和智能指针一样,重载了指针运算符,此处是QtPrivate::QSlotObjectBase对象
                obj->call(receiver, argv ? argv : empty_argv); // 调用槽函数
            }

            // Make sure the slot object gets destroyed before the mutex is locked again, as the
            // destructor of the slot object might also lock a mutex from the signalSlotLock() mutex pool,
            // and that would deadlock if the pool happens to return the same mutex.
            obj.reset();

            locker.relock();
        }
	} while (c != last && (c = c->nextConnectionList) != 0);
} while (list != &connectionLists->allsignals &&
    ((list = &connectionLists->allsignals), true));
}
// allsignals是一个无效list,因为信号可能触发了,但是没有connect对应的槽函数,所以就会返回这个链表

QSlotObjectBase是虚基类,call调用的是impl这个函数,FunctionPointer这个结构体的作用是可以接收不同种类的函数,成员函数,const成员函数,普通函数等等,至此,触发信号到调用到槽函数的流程已说明完毕

class QSlotObjectBase {
        QAtomicInt m_ref;
        // don't use virtual functions here; we don't want the
        // compiler to create tons of per-polymorphic-class stuff that
        // we'll never need. We just use one function pointer.
        typedef void (*ImplFn)(int which, QSlotObjectBase* this_, QObject *receiver, void **args, bool *ret);
        const ImplFn m_impl;
    protected:
        enum Operation {
            Destroy,
            Call,
            Compare,

            NumOperations
        };
    public:
        explicit QSlotObjectBase(ImplFn fn) : m_ref(1), m_impl(fn) {}

        inline int ref() Q_DECL_NOTHROW { return m_ref.ref(); }
        inline void destroyIfLastRef() Q_DECL_NOTHROW
        { if (!m_ref.deref()) m_impl(Destroy, this, nullptr, nullptr, nullptr); }

        inline bool compare(void **a) { bool ret = false; m_impl(Compare, this, nullptr, a, &ret); return ret; }
        inline void call(QObject *r, void **a)  { m_impl(Call,    this, r, a, nullptr); }
    protected:
        ~QSlotObjectBase() {}
    private:
        Q_DISABLE_COPY(QSlotObjectBase)
    };
   
template<typename Func, typename Args, typename R>
class QSlotObject : public QSlotObjectBase
{
    typedef QtPrivate::FunctionPointer<Func> FuncType;
    Func function;
    static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret)
    {
        switch (which) {
        case Destroy:
            delete static_cast<QSlotObject*>(this_);
            break;
        case Call:
            FuncType::template call<Args, R>(static_cast<QSlotObject*>(this_)->function,
                static_cast<typename FuncType::Object *>(r), a);
            break;
        case Compare:
            *ret = *reinterpret_cast<Func *>(a) == static_cast<QSlotObject*>(this_)->function;
            break;
        case NumOperations: ;
        }
    }
public:
    explicit QSlotObject(Func f) : QSlotObjectBase(&impl), function(f) {}
};
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QT信号和槽机制实现及源码阅读 的相关文章

随机推荐

  • uniapp开发app原生子窗体subNvue的使用

    用uniapp开发app的时候经常会有以下问题 1 覆盖原生导航栏 tabbar 的弹出层组件 比如侧滑菜单盖不住地图 视频 原生导航栏 比如 popup盖不住tabbar 2 弹出层内部元素可滚动 3 在map video等组件上的添加复
  • FPGA——按键消抖常用模板代码

    模板如下 define UD 1 module key jitter input clkin input key in output key value output 15 0 tout inner signal reg 1 0 key i
  • Angular1.x 基础入门

    一 Angular1 x概述 致力于单页面应用 single page application 不直接操作DOM元素 数据驱动为核心 以操作数据完成页面的一系列 二 Angular1 x特点 MVC MVC模式 Model 模型 业务数据
  • ts(TypeScript)常用语法(Omit、Pick、Partial、Required)

    ts TypeScript 常用语法 比如有一个联系人列表 export interface Contact name string 姓名 phone string 手机号 email string 邮箱 avatar string 头像
  • appium根据屏幕大小滑动界面driver.get_window_size()、driver.swipe()

    driver get window size 获取屏幕的宽 高 driver swipe 从坐标1滑动到坐标2 t毫秒时间内完成 上下滑动时 坐标的x值可以不变 只改变坐标y值的大小 左右滑动时 坐标的y值可以不变 只改变坐标x值的大小 上
  • 分布式系统与微服务的区别是什么?

    分布式系统和微服务是两个相关但不同的概念 它们都是在构建复杂的软件应用时使用的架构思想 分布式系统 分布式系统是指由多个独立的计算机或服务器通过网络连接共同工作 协同完成一个任务或提供一个服务 在分布式系统中 各个计算机节点可以分担任务的负
  • “华为杯”研究生数学建模竞赛2019年-【华为杯】D题:汽车行驶工况构建(附获奖论文和MATLAB代码实现)

    目录 摘 要 1 问题重述 2 模型假设 2 1 题目对模型给出的假设
  • Qt核心特性之 —— 「信号(Signal)与槽(Slot)」机制

    目录 1 Qt 与 Qt Creator简介 2 关于引用头文件的一些事儿 3 信号 Signal 与槽 Slot 机制 3 1 一个小例子 4 自定义信号与槽 4 1 运行效果 5 信号与槽的特性 6 Qt 4 版本以前 connect
  • linux 如何创建卷组

    1 创建一个物理卷 Pvcreate dev sd1 dev sd2 dev sd3 dev sd4 2 用刚才创建的物理卷创建一个卷组 Vgcreate 卷组名 dev sd1 dev sd2 dev sd3 dev sd4 3 创建逻辑
  • 第四章:树形结构的关联式容器(map+set)

    系列文章目录 文章目录 系列文章目录 前言 1 关联式容器与序列式容器 1 1 键值对 2 set的介绍 3 multiset的介绍 3 1 接口count与容器multiset 4 map的介绍 4 1 接口insert 4 2 oper
  • MySQL 报错 [ERROR] [FATAL] InnoDB: Table flags are 0 in the data dictionary but the flags in file

    本地装了 Wamp 的环境 启动时 MySQL 启动失败 查看启动失败的日志 日志如下 2021 08 21T12 46 57 183482Z 0 ERROR FATAL InnoDB Table flags are 0 in the da
  • nodejs学习-----封装异步API,学习回调函数

    1 回调函数学习 回调函数定义 使用者自己定义一个函数 实现这个函数的程序内容 然后把这个函数 入口地址 作为参数传入别人 或系统 的函数中 由别人 或系统 的函数在运行时来调用的函数 函数是你实现的 但由别人 或系统 的函数在运行时通过参
  • left join on多表关联_2周零基础搞定SQL——多表查询

    在上一篇文章里 我们学习了SQL的复杂查询 但是依然只是针对一个表的 但在实际工作中 我们需要的数据 往往分布在多个表中 所以为了更好的解决工作中的实际问题 今天我们一起来学习如何用SQL进行多表查询吧 1 表的加法 在之前的学习中 我们建
  • 4-2 过滤器法

    4 2 过滤器法 请参考 数据准备和特征工程 中的相关章节 调试如下代码 注意 本节内容因为要耗费比较大的内存 在线平台有可能无法支持 可以下载到本地执行 基础知识 from sklearn datasets import load iri
  • 运行ntpdate报错:Temporary failure in name resolution

    一 问题报错 忽然发现某台机器时间慢了些几分钟 之前没有搭建ntpd服务 目前都是使用的ntpdate加定时任务进行时间同步 直接执行ntpdate报错如下 ntpdate cn pool ntp org Exiting name serv
  • 大学四年,因为这8个网站,我成为同学眼中的学霸

    作者简介 CSDN top100 阿里云博客专家 华为云享专家 网络安全领域优质创作者 推荐专栏 对网络安全感兴趣的小伙伴可以关注专栏 网络安全入门到精通 大学期间 几乎每一个教过我的老师都反应 我的学习态度不好 我上课很少仔细听老师在讲什
  • DNF搭建服务器服务端搭建教程

    DNF搭建服务器服务端搭建教程 我是艾西 今天给大家分享下怎么样自己搭建一个DNF 前阵子体验了下其他GM搭建的服 那么对于自己搭建的好处在于出道即巅峰 想要什么武器就是一串代码命令的事情 下面我跟大家说一下需要准备那些东西 DNF服务端
  • java获取当前服务器系统默认得编码格式

    java文件中 可以通过下面方法获取执行这段代码的服务器系统的编码格式 System getProperty file encoding 输出的结果是String的字符串 例如 utf 8
  • Java之Spring

    目录 创建spring项目 存储bean对象到容器 spring 中 从spring中将bean取出 更简单的读取存储对象 存储bean对象 前置准备 添加注解存储 Bean 对象 获取bean对象 bean作用域和生命周期 定义 bean
  • QT信号和槽机制实现及源码阅读

    说明 QT的信号和槽采用观察者模式 Q OBJECT是提供信号和槽的基础 使用过connect第五个参数的可知 槽可以在信号发出的线程处理 也可以加入任务队列进行处理 但是在此只写了在触发线程处理的代码 如下是实现的类QT信号和槽 gt F