在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();}//同QT4QObject *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

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

5.QT5中的connect的实现相关推荐

  1. QT5中如何使用SQLite

    SQLite是一款开源轻量级的数据库软件,本文主要介绍了QT5中使用SQLite的实现方法,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 SQLite(sql)是一 ...

  2. 4.QT4中的connect的实现

    信号槽通过connect进行连接,connect的源码在qobject.cpp中 QT4中的connect的声明如下 static QMetaObject::Connection connect(co ...

  3. QT5中实现多窗口切换,并从子窗口返回数据

    该程序的功能:主窗口里有两个button和ltextEdit,按下下一页button后弹出子窗口并隐藏主窗口,按下退出Button后退出程序.子窗口有文本框lineEdit和button,按下子窗口的 ...

  4. Qt5 中 QWebEngineView 的使用,让桌面客户端和 web 端友好通信

    一.本文结构 Qt5 中 web 开发的基本配置,工程搭建. 加载 HTML 文件. Qt 界面窗口调用 HTML 中的 JavaScript 函数(Qt 调用 js) Qt 界面窗口获取 HTML ...

  5. qurlinfo在qt5中_QT5编译使用QFtp的方法步骤

    背景 使用 QNetworkAccessManager 可以实现 Ftp 的上传/下载功能,但它没有提供例如list.cd.remove.mkdir.rmdir.rename 等功能.这种情况下,我们 ...

  6. ORA-01436: 用户数据中的CONNECT BY 循环

    起始地     目的地     距离(公里) A             B             1000 A             C             1100 A           ...

  7. ITK:在二进制映像中标记Connect组件

    ITK:在二进制映像中标记Connect组件 内容提要 C++实现代码 内容提要 在二进制映像中标记连接的组件. C++实现代码 #include "itkLiThresholdImageF ...

  8. qt5中服务器文件传输,POST在Qt5中发送JSON文件的请求

    在Qt5的帮助下,通过POST请求将JSON文件发送到服务器时有点问题.我给你一张我的程序:POST在Qt5中发送JSON文件的请求 #include #include #include #inclu ...

  9. 数据库异常---ORA-01436: 用户数据中的 CONNECT BY loop in user data 循环

    数据库 ORA-01436: 用户数据中的 CONNECT BY  loop in user data  循环 技术qq交流群:JavaDream:251572072  教程下载,在线交流:创梦IT社 ...

最新文章

  1. 条形码?二维码?生成、解析都在这里!
  2. eyoucms添加内容
  3. Matplotlib实例教程(十七)3D山体图
  4. d3 tip mysql_mysql
  5. python魔法方法与函数_在Python中画图(基于Jupyter notebook的魔法函数)
  6. 设置ubuntu默认python3设置
  7. Android 注解的使用与注意事项
  8. 把一个 ASP.NET 程序转换为了 Web Services
  9. 恩施机器人编程_恩施安川机器人水切切割编程
  10. 调用sklearn包中的PLA算法[转载]
  11. awk的关联数组版本支持
  12. oracle jdk下载镜像
  13. OpenDrive里XY和ST
  14. 零度之下代码输入不了_瞧一瞧康一康吖!简单又好玩的代码
  15. 波士顿动力Spot mini,MIT 猎豹、宇树科技、蔚蓝四足机器人类别
  16. 【股票】成交量VOL隐含的交易秘密
  17. bittorrent_最好的免费BitTorrent客户
  18. 如何卸载当前最新版的奇某信天擎?
  19. 在PPT上使用开发工具的不同控件实现单选操作
  20. node-gyp rebuild 报错处理

热门文章

  1. Git 创建两个“本地分支”协同工作
  2. css实现一个写信的格式
  3. Swift中一个类中的枚举(enum)类型的数据该如何实现序列化(NSCoder)
  4. this computer does not support Intel Virtualization Technology (VT-x) .Haxm can'not be installed
  5. ORACLE查看当前连接用户的权限信息或者角色信息
  6. 用例子说明MVC 设计模式(以Objective-C 实现)
  7. exchange 2003队列清空方法参考
  8. 公积金贷款不受影响 组合贷款有特殊
  9. (十)MySQL日志
  10. jquery腾讯微博