5.QT5中的connect的实现

2023-10-26

在QT4中,解析信号槽是通过将信号槽的名字转化为字符串,然后通过connect解析该字符串,得到信号函数的相对序号和,然后创建信号connectionlist,但是,所有的检查都在运行时,通过解析字符串进行。 这意味着,如果信号槽的名称拼写错误,编译会成功,但是只是会建立空连接。

moctest::moctest()
{
    connect(this, SIGNAL(sigf1(double1)) , this, SLOT(slotf(double)));
    connect(this, &moctest::sigf2, this, &moctest::slotf2);
}

上述代码编译时,不会出现问题,但是在运行时,会提示错误

也就是说,当使用SIGNAL和SLOT后,无论宏里面写啥,编译时都不会检查

 

所以,为了添加编译时的检查,新版的connect使用了函数指针作为信号槽函数,使用了函数指针后,如果信号槽函数出现拼写错误,则会在编译时无法通过,而且还可以使用lambda表达式槽函数,因为lambda表达式返回的也是一个函数指针。

当函数指针拼写错误时,QT在编译前就会提示找不到成员函数

新版connect的函数声明

template<typename PointerToMemberFunction>
static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection);

使用connect时,一般是这样的

connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);

第二个和第四个是函数指针,将模板参数PointerToMemberFunction变成函数的类型

connect的具体实现如下

template <typename Func1, typename Func2>
    static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
                                     const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
                                     Qt::ConnectionType type = Qt::AutoConnection)
    {
        typedef QtPrivate::FunctionPointer<Func1> SignalType;
        typedef QtPrivate::FunctionPointer<Func2> SlotType;

        Q_STATIC_ASSERT_X(QtPrivate::HasQ_OBJECT_Macro<typename SignalType::Object>::Value,
                          "No Q_OBJECT in the class with the signal");

        //compilation error if the arguments does not match.
        Q_STATIC_ASSERT_X(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount),
                          "The slot requires more arguments than the signal provides.");
        Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments<typename SignalType::Arguments, typename SlotType::Arguments>::value),
                          "Signal and slot arguments are not compatible.");
        Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<typename SlotType::ReturnType, typename SignalType::ReturnType>::value),
                          "Return type of the slot is not compatible with the return type of the signal.");

        const int *types = nullptr;
        if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
            types = QtPrivate::ConnectionTypes<typename SignalType::Arguments>::types();

        return connectImpl(sender, reinterpret_cast<void **>(&signal),
                           receiver, reinterpret_cast<void **>(&slot),
                           new QtPrivate::QSlotObject<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
                                           typename SignalType::ReturnType>(slot),
                            type, types, &SignalType::Object::staticMetaObject);
    }

先看函数名

template <typename Func1, typename Func2>
static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
                                     const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
                                     Qt::ConnectionType type = Qt::AutoConnection)

其中Func1和Func2是模板参数,表示成员函数的类型,QtPrivate::FunctionPointer<Func1>::Object *sender是发送信号的对象,signal是函数指针,后两个参数同理

QtPrivate::FunctionPointer的实现在qobjectdefs_impl.h中,该模板类主要用来提供元数据

    template<class Obj, typename Ret, typename... Args> struct FunctionPointer<Ret (Obj::*) (Args...)>
    {
        typedef Obj Object;//包含成员函数的类
        typedef List<Args...>  Arguments;//代表参数列表。
        typedef Ret ReturnType;
        typedef Ret (Obj::*Function) (Args...);
        enum {ArgumentCount = sizeof...(Args), IsPointerToMemberFunction = true};//ArgumentCount表示函数的参数数目,
        template <typename SignalArgs, typename R>
        static void call(Function f, Obj *o, void **arg) {//使用给定参数调用该成员函数f。
            FunctorCall<typename Indexes<ArgumentCount>::Value, SignalArgs, R, Function>::call(f, o, arg);
        }
    };

然后是两个typedef

typedef QtPrivate::FunctionPointer<Func1> SignalType;
typedef QtPrivate::FunctionPointer<Func2> SlotType;

当把信号函数和槽函数传入后,根据FunctionPointer会解析出信号槽函数的返回值、所在类和形参列表

紧接着是四个Q_STATIC_ASSERT_X断言,必须包含Q_OBJECT,信号的参数数量必须大于等于槽函数的参数数量,信号的参数和槽函数的参数必须得兼容(类型一致或可以转化),信号槽的返回值的类型也必须得兼容

之后是如果连接方式是QueuedConnection或者BlockingQueuedConnection,那么要获取入队的参数信息,同QT4

最后执行connectImpl

 return connectImpl(sender, reinterpret_cast<void **>(&signal),
                           receiver, reinterpret_cast<void **>(&slot),
                           new QtPrivate::QSlotObject<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
                                           typename SignalType::ReturnType>(slot),
                            type, types, &SignalType::Object::staticMetaObject);

这里面用new QtPrivate::QSlotObject<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value, typename SignalType::ReturnType>(slot)初始化QtPrivate::QSlotObjectBase *slotObj

Func2表示槽函数的类型

typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,这里面,List_Left <L,N>以一个列表和一个数字作为参数并返回一个列表,列表由列表的前N个元素组成,所以这里面返回的是一个列表,列表里有好多类型,信号的参数个数有可能多于SlotType::ArgumentCount,但是最多取SlotType::ArgumentCount个

typename SignalType::ReturnType表示信号函数的返回值类型

指定完三个模板参数后,传入槽函数的指针slot

 

connectImpl的实现

QMetaObject::Connection QObject::connectImpl(const QObject *sender, void **signal,
                                             const QObject *receiver, void **slot,
                                             QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
                                             const int *types, const QMetaObject *senderMetaObject)
{
    if (!signal) {
        qWarning("QObject::connect: invalid nullptr parameter");
        if (slotObj)
            slotObj->destroyIfLastRef();
        return QMetaObject::Connection();
    }//判空

    int signal_index = -1;
    void *args[] = { &signal_index, signal };
    for (; senderMetaObject && signal_index < 0; senderMetaObject = senderMetaObject->superClass()) {
        senderMetaObject->static_metacall(QMetaObject::IndexOfMethod, 0, args);//调用MOC文件中的static_metacall计算信号的相对序号
        if (signal_index >= 0 && signal_index < QMetaObjectPrivate::get(senderMetaObject)->signalCount)
            break;
    }
    if (!senderMetaObject) {//判断发送信号的元对象是否有效
        qWarning("QObject::connect: signal not found in %s", sender->metaObject()->className());
        slotObj->destroyIfLastRef();
        return QMetaObject::Connection(0);
    }
    signal_index += QMetaObjectPrivate::signalOffset(senderMetaObject);//获取信号的绝对序号
    return QObjectPrivate::connectImpl(sender, signal_index, receiver, slot, slotObj, type, types, senderMetaObject);//继续调用QObjectPrivate::connectImpl
}

思路依旧是获取信号的绝对序号,和QT4类似

 

QObjectPrivate::connectImpl的实现

QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int signal_index,
                                             const QObject *receiver, void **slot,
                                             QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
                                             const int *types, const QMetaObject *senderMetaObject)
{
    if (!sender || !receiver || !slotObj || !senderMetaObject) {
        const char *senderString = sender ? sender->metaObject()->className()
                                          : senderMetaObject ? senderMetaObject->className()
                                          : "Unknown";
        const char *receiverString = receiver ? receiver->metaObject()->className()
                                              : "Unknown";
        qWarning("QObject::connect(%s, %s): invalid nullptr parameter", senderString, receiverString);
        if (slotObj)
            slotObj->destroyIfLastRef();
        return QMetaObject::Connection();
    }//同QT4

    QObject *s = const_cast<QObject *>(sender);
    QObject *r = const_cast<QObject *>(receiver);

    QOrderedMutexLocker locker(signalSlotLock(sender),
                               signalSlotLock(receiver));

    if (type & Qt::UniqueConnection && slot && QObjectPrivate::get(s)->connections.loadRelaxed()) {//使得每个连接都是UniqueConnection,和QT4类似
        QObjectPrivate::ConnectionData *connections = QObjectPrivate::get(s)->connections.loadRelaxed();
        if (connections->signalVectorCount() > signal_index) {
            const QObjectPrivate::Connection *c2 = connections->signalVector.loadRelaxed()->at(signal_index).first.loadRelaxed();

            while (c2) {
                if (c2->receiver.loadRelaxed() == receiver && c2->isSlotObject && c2->slotObj->compare(slot)) {
                    slotObj->destroyIfLastRef();
                    return QMetaObject::Connection();
                }
                c2 = c2->nextConnectionList.loadRelaxed();
            }
        }
        type = static_cast<Qt::ConnectionType>(type ^ Qt::UniqueConnection);
    }

    std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};//创建一个Connection,和QT4类似
    c->sender = s;
    c->signal_index = signal_index;
    QThreadData *td = r->d_func()->threadData;
    td->ref();
    c->receiverThreadData.storeRelaxed(td);
    c->receiver.storeRelaxed(r);
    c->slotObj = slotObj;//区别,这里使用的是基类QSlotObjectBase和子类QSlotObject来填充connection结构,其余的和QT4类似
    c->connectionType = type;
    c->isSlotObject = true;
    if (types) {
        c->argumentTypes.storeRelaxed(types);
        c->ownArgumentTypes = false;
    }

    QObjectPrivate::get(s)->addConnection(signal_index, c.get());
    QMetaObject::Connection ret(c.release());
    locker.unlock();

    QMetaMethod method = QMetaObjectPrivate::signal(senderMetaObject, signal_index);
    Q_ASSERT(method.isValid());
    s->connectNotify(method);

    return ret;
}

上述两段代码和QT4基本相同,都是计算信号的绝对序号,然后创建信号的connectionlist并添加,和QT4不同的是,connection的采用的是基类QSlotObjectBase和子类QSlotObject来填充connection结构,而不是直接使用函数指针

 

QSlotObjectBase和QSlotObject的定义在qobjectdefs_impl.h

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() noexcept { return m_ref.ref(); }
        inline void destroyIfLastRef() noexcept
        { 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_MOVE(QSlotObjectBase)
    };

    // implementation of QSlotObjectBase for which the slot is a pointer to member function of a QObject
    // Args and R are the List of arguments and the returntype of the signal to which the slot is connected.
    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) {}//QSlotObject的构造函数将槽函数的地址传入并初始化function,使得能正确调用对应的槽函数
    };

如果使用虚函数,每个实例都需要创建一个虚拟表,该表不仅包含指向虚拟函数的指针,而且还包含许多我们不需要的信息,这将导致大量多余的数据。所以,将QSlotObjectBase设置为普通类。QSlotObject的构造函数将槽函数的地址传入并初始化function,使得能正确调用对应的槽函数

 

参考

Qt5.14源码

https://qtguide.ustclug.org/

https://woboq.com/blog/how-qt-signals-slots-work-part2-qt5.html

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

5.QT5中的connect的实现 的相关文章

随机推荐

  • Mac格式化移动硬盘DiskUtil

    可以使用如下命令 diskutil list dev disk4 external physical TYPE NAME SIZE IDENTIFIER 0 GUID partition scheme 4 0 TB disk4 1 EFI
  • django开发电子商城(三)django内置分页

    1 更新数据库表 修改models py中的Product类 运行命令 完成数据库更新 2 在admin界面增加相关数据 3 编辑列表函数 views py 增加分页功能 4 增加路由 编辑urls py
  • .net5 不支持winform_深度探秘.NET 5.0

    2020 中国 NET 开发者峰会正式启动 欢迎大家提交演讲主题或者购买超级早鸟票 今年11月10号 NET 5 0 如约而至 这是 NET All in one后的第一个版本 虽然不是LTS Long term support 版本 但是
  • 递推与递归

    递推 例一 仅展示核心代码 int main cin gt gt N Bigint f 5010 f 1 Bigint 1 f 2 Bigint 2 for int i 3 i lt N i f i f i 2 f i 1 f N prin
  • JSON.parse()与JSON.stringify()的区别

    json stringfy 将对象 数组转换成字符串 json parse 将字符串转成json对象 json stringfy 语法 JSON stringify value replacer space 参数 value 是必选字段 就
  • vue项目3-通过与拒绝审核

    这样的需求 点击拒绝审核状态变为审核不通过 反之为审核通过 首先进行数据处理 显示当前内容特别注意 给每一项添加 examine 属性 让其随着status改变而改变 这也为之后调用接口改变status进而改变examine埋下了伏笔 上面
  • 归并算法详解

    归并算法的本质是将待排序序列分为两部分 依次对分得的两个部分再次使用归并排序 之后再对其进行合并 接下来对待排序列A 0 A 1 A 2 A n 1 用归并排序思想进行排序 算法实现 1 将序列分为两部分 其中一部分对应的索引区间为 sta
  • springboot整合mybatis将sql打印到日志

    在前台请求数据的时候 sql语句一直都是打印到控制台的 有一个想法就是想让它打印到日志里 该如何做呢 见下面的mybatis配置文件
  • 计算机网络的种种

    以太网帧结构 类型为0x0800时 表示网络层使用的是IP协议 以太网传送数据时 每两个帧之间存在帧间隙IFG Inter Frame Gap 或者说IPG Inter Packet Gap 帧间隙的作用是使介质中的信号处于稳定状态 同时让
  • Linux系统Conda安装paddle-GPU后出现The third-party dynamic library (libcudnn.so) 报错

    在Linux系统下 使用conda环境安装GPU版本的Paddle 安装后使用官方检测程序python c import paddle paddle utils run check 检测GPU是否工作正常 出现如下报错 The third
  • uniapp小程序上传图片裁剪效果demo(整理)

  • 朋友圈--并查集

    LeetCode 朋友圈 班上有 N 名学生 其中有些人是朋友 有些则不是 他们的友谊具有是传递性 如果已知 A 是 B 的朋友 B 是 C 的朋友 那么我们可以认为 A 也是 C 的朋友 所谓的朋友圈 是指所有朋友的集合 给定一个 N N
  • 关于 Pytorch 学习的一些小困惑的理解

    目录 小记 20 2 27 model model cuda 和model model to cuda 是等价的嘛 模型会自动初始化嘛 训练好的网络模型是如何保存的 又是如何再加载的 哪些部分可以移到GPU上运算 关于torch nn mu
  • 【毕业设计】SVM 分类器和深度学习的方法对多种无人机型号进行分类识别【程序+论文】

    利用 SVM 分类器和深度学习的方法对多种无人机型号进行分类识别 但是对于同种型 号无人机不同个体识别率不高 射频指纹 Radio Frequency Distinct Native Attribute RFDNA 的提出为解决这一问题提供
  • RabbitMQ--交换机、队列、消息

    交换机 autoDelete 如果设置为true 唯一的一个交换机或者队列解绑 那么该队列将会被自动删除 交换机类型 faout 经过交换机的消息直接转到所有与这个交换器绑定的队列中 无视rounting key的存在 direct 经过交
  • 国密:SM2公私钥加签验签

    一 工具类 POM中增加hutool
  • jdbc mysql 重连_JDBC实现Mysql自动重连机制的方法详解

    JDBC是一个用于连接和访问各种数据库的应用编程接口 它可以提供Java程序和各种数据库之间的连接服务 以下是成都seo技术频道编辑带来的实现Mysql自动重连机制的JDBC方法的详细说明 日志 使用连接器 J连接属性 自动连接 真 来避免
  • 我的Docker部署笔记

    Centos7下安装Docker 1 root账户登录 查看内核版本 root localhost uname a Linux localhost localdomain 3 10 0 1160 el7 x86 64 1 SMP Mon O
  • var与let的区别【一看就懂的知识】

    今天偶尔翻看了一本书 JavaScript编程精解 在函数这一章节中难免会看到 作用域 这个字眼 之前对于作用域的概念本就不是很敏感 也就匆匆略过了 反过来看时 才明白这是一个比较重要的点 下面借作用域这一概念深入了解一下本文的重点 var
  • 5.QT5中的connect的实现

    在QT4中 解析信号槽是通过将信号槽的名字转化为字符串 然后通过connect解析该字符串 得到信号函数的相对序号和 然后创建信号connectionlist 但是 所有的检查都在运行时 通过解析字符串进行 这意味着 如果信号槽的名称拼写错