参考文献

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

之前看过参考文献和源码,但是没有记录,很容易遗忘,随记录一下,也算是加深下印象
qt5之前的信号与槽实现方式
  • 下面是一个官方的例子
class Counter : public QObject
{Q_OBJECTint m_value;
public:int value() const { return m_value; }
public slots:void setValue(int value);
signals:void valueChanged(int newValue);
};// 在cpp文件我们实现了setValue()
void Counter::setValue(int value)
{if (value != m_value) {m_value = value;emit valueChanged(value);}
}// 下面是hi调用方式
Counter a, b;
QObject::connect(&a, SIGNAL(valueChanged(int)),&b, SLOT(setValue(int)));a.setValue(12);  // a.value() == 12, b.value() == 12// ps: 这种原始的信号和槽连接方式自1992年就没变过了。。org
MOC,the meta Object Compiler
  • Qt的signal/slots和属性系统是基于此功能在运行时反射objects。反射意味着可以去列举一个对象的方法和属性,并且可以知道它的所有相关信息,比如参数类型。
  • QtScript和QML就是依靠这种能力的
  • C++没有原生的实现反射,所以qt为此提供了工具。这个工具就是MOC。这是一个代码生成器。
  • MOC解析头文件,并且生成额外的c++file,这个文件作为这个项目的一部分参与编译。这个生成的C++文件包含反射所需要的所有信息。
Qt的关键字
// Qt有一些关键字作为C++的扩展,这些关键字是定义在qobjectdefs.h
#define signals public
#define slots /* nothing */
// 这些宏是很简单的,唯一的目的就是让moc看见它// Q_OBJECT定义了一簇函数和一个静态成员变量,这些函数是实现在moc生成的文件中。
#define Q_OBJECT \
public: \static const QMetaObject staticMetaObject; \virtual const QMetaObject *metaObject() const; \virtual void *qt_metacast(const char *); \virtual int qt_metacall(QMetaObject::Call, int, void **); \QT_TR_FUNCTIONS /* translations helper */ \
private: \Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
///emit 是一个空的宏,无需moc解析,换句话说emit是一个可选的,没有任何含义。就是一个提示
#define emit /*nothing*/// 这个宏是将参数转换成字符串Q_CORE_EXPORT const char *qFlagLocation(const char *method);
#ifndef QT_NO_DEBUG
# define QLOCATION "\0" __FILE__ ":" QTOSTRING(__LINE__)
# define SLOT(a)     qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a)   qFlagLocation("2"#a QLOCATION)
#else
# define SLOT(a)     "1"#a
# define SIGNAL(a)   "2"#a
#endif
下面我们将看看moc生成的代码
const QMetaObject Counter::staticMetaObject = {{ &QObject::staticMetaObject, qt_meta_stringdata_Counter.data,qt_meta_data_Counter,  qt_static_metacall, Q_NULLPTR, Q_NULLPTR}
};// QObject::d_ptr->metaObject 是仅仅用于动态元目标(QML Object)
const QMetaObject *Counter::metaObject() const
{return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}// staticMetaObject是被构造为只读数据,QMetaObject是定义在qobjectdefs.h文件中
// 这个d间接的象征,所有的成员都应该是私有的,这里没有私有,是为了保持POD并且允许静态初始化。
struct QMetaObject
{/* ... Skiped all the public functions ... */enum Call { InvokeMetaMethod, ReadProperty, WriteProperty, /*...*/ };struct { // private dataconst QMetaObject *superdata;const QByteArrayData *stringdata;const uint *data;typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);StaticMetacallFunction static_metacall;const QMetaObject **relatedMetaObjects;void *extradata; //reserved for future use} d;
};
下面介绍反射表
static const uint qt_meta_data_Counter[] = {// content:7,       // revision0,       // classname0,    0, // classinfo2,   14, // methods          // 有多少个信号和槽方法,索引位置在哪0,    0, // properties0,    0, // enums/sets0,    0, // constructors0,       // flags1,       // signalCount      // 有多少个信号// signals: name, argc, parameters, tag, flags1,    1,   24,    2, 0x06 /* Public */,// slots: name, argc, parameters, tag, flags4,    1,   27,    2, 0x0a /* Public */,// signals: parametersQMetaType::Void, QMetaType::Int,    3,// slots: parametersQMetaType::Void, QMetaType::Int,    5,0        // eod// 前面13个int构成了文件头,当那里有两列数据时,第一列是数量,第二列是
// description 开始的位置
// 方法的描述是由5个int组成的,第一个是name,在string table的index。
// 第二个是参数的数量,第三个是描述参数的索引。后面2个值暂时忽略。
};
下面将介绍string table
struct qt_meta_stringdata_Counter_t {QByteArrayData data[6];char stringdata0[46];
};
#define QT_MOC_LITERAL(idx, ofs, len) \Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \qptrdiff(offsetof(qt_meta_stringdata_Counter_t, stringdata0) + ofs \- idx * sizeof(QByteArrayData)) \)
static const qt_meta_stringdata_Counter_t qt_meta_stringdata_Counter = {{
QT_MOC_LITERAL(0, 0, 7), // "Counter"
QT_MOC_LITERAL(1, 8, 12), // "valueChanged"
QT_MOC_LITERAL(2, 21, 0), // ""
QT_MOC_LITERAL(3, 22, 8), // "newValue"
QT_MOC_LITERAL(4, 31, 8), // "setValue"
QT_MOC_LITERAL(5, 40, 5) // "value"},"Counter\0valueChanged\0\0newValue\0setValue\0""value"
};
#undef QT_MOC_LITERAL
介绍信号
// SIGNAL 0
void Counter::valueChanged(int _t1)
{void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
// 数组a的第一个元素是返回值。因为返回值是void,所以是0
// 传递给activate的第三个参数是信号的索引
调用一个槽
void Counter::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{if (_c == QMetaObject::InvokeMetaMethod) {Q_ASSERT(staticMetaObject.cast(_o));Counter *_t = static_cast<Counter *>(_o);Q_UNUSED(_t)switch (_id) {case 0: _t->valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;case 1: _t->setValue((*reinterpret_cast< int(*)>(_a[1]))); break;default: ;}
关于index
  • 对于每一个QMetaObject,槽和信号、其他回调方法都会被提供一个index。从0开始,它们是按照信号在前、随后是槽以及其他的回调方法来排序的。不包含父类的index。
  • 但是通常我们不想去知道全局索引,包含继承链的所有方法。我们为相对偏移增加一个offset来得到绝对index。
  • 这个索引是在public API中使用,通过函数QMetaObject::indexof{Signal, SLOT, Method}方法得到。
Connecting 是如何工作的?
  • Qt建立一个链接首先要做的事情是:查找信号和槽的索引,Qt将遍历meta object的string table去查找对应的索引。
  • 随后一个QObjectPrivate::Connection object是被创建和增加到内部list的。
  • 这个connection的设计要求
    • 通过给予的signal index快速访问这个connection
    • 一个信号可以链接多个槽,所以一个信号都需要一个连接到槽的list
    • 每一个connection必须要包含receiver object。和槽的索引
    • 当receiver object 摧毁时,希望connect也能被摧毁。因此每一个receiver object 需要知道谁被链接了。
  • 每一个Object都有一个connection vector,这个vector存储了每一个信号的connect list
  • 每一个object还有链表来存储,这个object 的槽被链接的connection。这是便于删除。
  • 这个linked list的实现是通过connection类里面的prev和next nodes来实现的。
  • note: prev 是指向指针的指针。这是因为我们不是真正的指向上一个节点,而是上一个节点中指向下一个节点的指针(自己?)
struct QObjectPrivate::Connection
{QObject *sender;QObject *receiver;union {StaticMetaCallFunction callFunction;QtPrivate::QSlotObjectBase *slotObj;};// The next pointer for the singly-linked ConnectionListConnection *nextConnectionList;//senders linked listConnection *next;Connection **prev;QAtomicPointer<const int> argumentTypes;QAtomicInt ref_;ushort method_offset;ushort method_relative;uint signal_index : 27; // In signal range (see QObjectPrivate::signalIndex())ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blockingushort isSlotObject : 1;ushort ownArgumentTypes : 1;Connection() : nextConnectionList(0), ref_(2), ownArgumentTypes(true) {//ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection}~Connection();int method() const { return method_offset + method_relative; }void ref() { ref_.ref(); }void deref() {if (!ref_.deref()) {Q_ASSERT(!receiver);delete this;}}
};
Sender
|————————————————|       _________________________....... QObjectConnectionListVector
|connectionList  |------>|__1__|__2__|__3__|__4__|......
|________________|              _______|  |____________ ___|__________       ||__________________|      ||first            |last
Receiver                ________|__________       |     ____________________
|            |          |                  |      |     |                   |
| senderList |--------->| connection *next |----------->|connection *next   |-----> null
|____________|<---------| connection **prev|<-----------|connection **prev  ||__________________|      |     |___________________|                 |                 |________|___________<------        |__________________|
下面是信号发射的逻辑
void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,void **argv)
{activate(sender, QMetaObjectPrivate::signalOffset(m), local_signal_index, argv);/* We just forward to the next function here. We pass the signal offset of* the meta object rather than the QMetaObject itself* It is split into two functions because QML internals will call the later. */
}void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)
{int signal_index = signalOffset + local_signal_index;/* The first thing we do is quickly check a bit-mask of 64 bits. If it is 0,* we are sure there is nothing connected to this signal, and we can return* quickly, which means emitting a signal connected to no slot is extremely* fast. */if (!sender->d_func()->isSignalConnected(signal_index))return; // nothing connected to these signals, and no spy/* ... Skipped some debugging and QML hooks, and some sanity check ... *//* We lock a mutex because all operations in the connectionLists are thread safe */QMutexLocker locker(signalSlotLock(sender));/* Get the ConnectionList for this signal.  I simplified a bit here. The real code* also refcount the list and do sanity checks */QObjectConnectionListVector *connectionLists = sender->d_func()->connectionLists;const QObjectPrivate::ConnectionList *list =&connectionLists->at(signal_index);QObjectPrivate::Connection *c = list->first;if (!c) continue;// We need to check against last here to ensure that signals added// during the signal emission are not emitted in this emission.QObjectPrivate::Connection *last = list->last;/* Now iterates, for each slot */do {if (!c->receiver)continue;QObject * const receiver = c->receiver;const bool receiverInSameThread = QThread::currentThreadId() == receiver->d_func()->threadData->threadId;// determine if this connection should be sent immediately or// put into the event queueif ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)|| (c->connectionType == Qt::QueuedConnection)) {/* Will basically copy the argument and post an event */queued_activate(sender, signal_index, c, argv);continue;} else if (c->connectionType == Qt::BlockingQueuedConnection) {/* ... Skipped ... */continue;}/* Helper struct that sets the sender() (and reset it backs when it* goes out of scope */QConnectionSenderSwitcher sw;if (receiverInSameThread)sw.switchSender(receiver, sender, signal_index);const QObjectPrivate::StaticMetaCallFunction callFunction = c->callFunction;const int method_relative = c->method_relative;if (c->isSlotObject) {/* ... Skipped....  Qt5-style connection to function pointer */} else if (callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {/* If we have a callFunction (a pointer to the qt_static_metacall* generated by moc) we will call it. We also need to check the* saved metodOffset is still valid (we could be called from the* destructor) */locker.unlock(); // We must not keep the lock while calling use codecallFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv);locker.relock();} else {/* Fallback for dynamic objects */const int method = method_relative + c->method_offset;locker.unlock();metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv);locker.relock();}// Check if the object was not deleted by the slotif (connectionLists->orphaned) break;} while (c != last && (c = c->nextConnectionList) != 0);
}

Qt信号与槽实现方式相关推荐

  1. Qt信号和槽连接方式的选择

    看了下Qt的帮助文档,发现connect函数最后还有一个缺省参数. connect函数原型是这样的: QMetaObject::Connection QObject::connect(const QO ...

  2. Qt信号与槽的五种连接方式

    qt信号与槽的五种连接方式: 1.默认连接:如果是在同一线程等价于直连,在不同线程等价于队列连接 2.直连:信号在哪,在哪个线程执行(最好只在同一线程中用) 3.队列连接: 槽在哪就在哪个线程执行 ( ...

  3. 【Qt】Qt信号与槽使用不当,使程序崩溃

    问题描述 跨线程使用Qt信号和槽,信号发送时间间隔小于槽函数处理时间时,造成程序崩溃. 原因分析 跨线程使用Qt信号和槽时,connect默认是QueuedConnection,队列连接方式. 信号传 ...

  4. QT信号与槽——观察者模式——回调函数

    QT信号与槽--观察者模式--回调函数 1.QT信号与槽机制 1.1信号本质 信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时候 Qt 对应的窗口类会发出某个信号.比如 ...

  5. qt信号与槽连接的书写规范

    环境 : vs2015 + qt  5.9.9 Qt信号和槽连接失败原因主要有以下几点: 1.槽函数并没有声明在类的public slots(或private slots或protected slot ...

  6. Qt信号与槽使用方法总结

    前言 在图形界面编程中QT是为首选,组件之间如何实现通信是核心的技术内容.Qt 使用了信号与槽的机制,非常的高效.简单.易学,方便开发者的使用.本文详细的介绍了Qt 当中信号与槽的概念,并演示了各种信 ...

  7. Qt信号与槽传递自定义数据类型——两种解决方法

    Qt信号与槽传递自定义数据类型--两种解决方法 参考文章: (1)Qt信号与槽传递自定义数据类型--两种解决方法 (2)https://www.cnblogs.com/tid-think/p/9300 ...

  8. QT 信号与槽 最简单例子

    QT  信号与槽 最简单例子 main.cpp 和 my_head.h源码: [cpp] view plaincopy #ifndef MY_HEAD_H #define MY_HEAD_H #inc ...

  9. QT信号与槽(自定义带参数的信号)

    关于QT信号与槽的问题其实每个初学QT的人都会遇到,当时我需要做一个带界面的demo,在信号和槽的问题上,我需要的想法是让槽可以有参数的进行操作,但是系统内置的clicked()信号是不含参数的,这对 ...

  10. Qt信号与槽传递QList动态数组

    Qt信号与槽传递QList动态数组 根据实验,测试程序见下: - QString的QList动态数组能够通过signal-slot机制进行传递: - 自定义结构的QList动态数组也能通过signal ...

最新文章

  1. zookeeper java.env_zookeeper在生产环境中的配置(zookeeper3.6)
  2. Android版本介绍
  3. C语言 位移运算符的使用
  4. 13-MySQL面向对象设计:数据表与Java对象对应关系
  5. Linux文件锁flock
  6. CI框架中pdo的使用方法
  7. (转载)NSOperation and NSOperationQueue教程(翻译)
  8. python 项目环境包的名称和版本导出和导入
  9. TImage、TPaintBox、TPicture、TBitmap、TCanvas、TGraphic 的关系与区别
  10. mysql双一参数_MySQL 的双1设置-数据安全的关键参数(案例分享)
  11. bzoj1218[HNOI2003]激光炸弹
  12. 数据结构11——KMP
  13. android studio for android learning (五) 最新Activity理解与其生命周期
  14. 微信小程序开发(后端 Java)
  15. fences 桌面整理,超赞,强烈推荐
  16. 色纯度(purity)主波长(WD)计算软件(升级版)
  17. uint8_t / uint16_t / uint32_t /uint64_t 这些数据类型是什么?
  18. Android中的RAM、ROM、SD卡以及各种内存的区别
  19. 【网络】Wireshark分析RST消息
  20. Unveiling causal interactions in complex systems(揭示复杂系统中的因果交互作用)

热门文章

  1. python调用通达信函数用户指标_最新最全通达信公式教程大全(函数-指标-实例)...
  2. 开源在线视频播放器flowplayer
  3. 开源代码授权Licence说明
  4. html怎么把音乐播放器放到中间,怎么把音乐播放器放到自己的博客首页面上去?...
  5. 3Dmax各类问题汇总及其完整解决方法
  6. 记录一次爬取淘宝/天猫评论数据的过程
  7. python入门视频教程
  8. 个人取得工资、薪金所得应当如何缴纳个人所得税
  9. 韦根协议c语言,韦根协议(26位)及其读取算法
  10. NOI题库答案(1.5 编程基础之循环控制) AC