QT学习(2):信号槽机制

目录

    • 信号槽机制
    • 定义
    • 连接
      • 一、标准connect链接
        • 1、QObject::connect
        • 2、QMetaObjectPrivate::connect
        • 3、信号槽容器Conntion、ConnectionList、SignalVector、ConnectionData
      • 二、函数指针链接(qt5后)
      • 三、Lamba表达式
    • 触发

信号槽机制

信号槽是观察者模式的一种实现,订阅-发布。一个信号就是一个能够被观察的事件,一个槽就是一个观察者,被观察者-多观察者。信号发出者通过遍历信号槽容器来通知所有连接的槽函数。

信号槽本质上是两个对象的函数地址映射单线程下,相当于函数指针调用;多线程下,发送者将槽函数的调用转化为一次调用事件放入事件循环队列,接收者线程执行到下一次事件处理时,调用相应的函数。

信号槽的效率比直接调用函数慢十倍

定义

在一个类中通过signals和slots宏定义信号和槽函数,两个都是qobjectdefs.h中定义的宏。signals最终被替换为publicslots最终被替换为空白,所以signals:即public:public slots:即public:。也就是说从C++的角度来看,信号是一个public属性的成员函数,函数体由MOC生成,定义在moc文件中,而槽函数是一个对应属性的成员函数,函数体由自己定义。

#define slots Q_SLOTS
#define signals Q_SIGNALS
# define Q_SLOTS QT_ANNOTATE_ACCESS_SPECIFIER(qt_slot)
# define Q_SIGNALS public QT_ANNOTATE_ACCESS_SPECIFIER(qt_signal)
# define QT_ANNOTATE_ACCESS_SPECIFIER(x)

QDBusAdaptorConnector类中名为relaySignal的信号,MOC编译器编译过后在moc_qdbusadaptorconnector.cpp中生成的信号函数体QDBusAdaptorConnector::relaySignal,在函数内部调用QMetaObject::activate来触发连接的槽函数。

// SIGNAL 0
void QDBusAdaptorConnector::relaySignal(QObject * _t1, const QMetaObject * _t2, int _t3, const QVariantList & _t4)
{
//----------_a是返回值和参数--------------------void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)), const_cast<void*>(reinterpret_cast<const void*>(&_t2)), const_cast<void*>(reinterpret_cast<const void*>(&_t3)), const_cast<void*>(reinterpret_cast<const void*>(&_t4)) };QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

连接

QT使用connect()函数连接信号槽,connect()的最后一个参数将指定连接类型:

  • Qt::DirectConnection:直接连接意味着槽函数将在信号发出的线程直接调用
  • Qt::QueuedConnection:队列连接意味着向接受者所在线程发送一个事件,该线程的事件循环将获得这个事件,然后之后的某个时刻调用槽函数
  • Qt::BlockingQueuedConnection:阻塞的队列连接就像队列连接,但是发送者线程将会阻塞,直到接受者所在线程的事件循环获得这个事件,槽函数被调用之后,函数才会返回
  • Qt::AutoConnection:自动连接(默认)意味着如果接受者所在线程就是当前线程,则使用直接连接;否则将使用队列连接

一、标准connect链接

connect(this, SIGNAL(signalOne(int)), this, SLOT(slotOne(int)));
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)

这个参数类型的connect()通过SIGNAL和SLOT两个宏将信号和槽转换为字符串,qFlagLocation是将字符串存储到QThreadData的flaggedSignatures成员中用于定位,最后返回原字符串。

# define SLOT(a)     qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a)   qFlagLocation("2"#a QLOCATION)
Q_CORE_EXPORT const char *qFlagLocation(const char *method);
# define QLOCATION "\0" __FILE__ ":" QT_STRINGIFY(__LINE__)
const char *qFlagLocation(const char *method)
{QThreadData *currentThreadData = QThreadData::current(false);if (currentThreadData != nullptr)currentThreadData->flaggedSignatures.store(method);return method;
}

将信号槽的函数名称按照相应规则组合成字符串,通过connect解析字符串,分别获取信号槽的绝对索引,然后将信号槽的链接关系添加到信号槽容器中。所以信号槽的检查都在运行时解析字符串的方式进行,即使拼写错误,也可以编译成功,只会在运行时报错。

1、QObject::connect
  1. 检查四个参数是否有空值,有则打印错误并返回。
  2. check_signal_macro检查signal是否被成功转换为对应格式的字符串(第二个参数是否使用了SIGNAL宏,而不是SLOT或其它),判断signal的首字符是否为2,其它参数用来打印错误信息。
  3. 获取signal的相对索引,加上偏移值得到绝对索引。
  4. check_method_code检查method是否被成功转换为对应格式的字符串(第四个参数是否使用了SIGNAL宏或SLOT宏,而不是其它,第四个可以为SIGNAL宏,即信号连接信号),判断signal的首字符是否为1或2,其它参数用来打印错误信息。
  5. 根据method是信号还是槽分别获取method的相对索引。
  6. 检查参数是否匹配。
  7. 检查连接方式。
  8. 调用QMetaObject::Connection存储signalmethod信息到信号槽容器。
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,const QObject *receiver, const char *method,Qt::ConnectionType type)
{//检查四个参数是否有空if (sender == nullptr || receiver == nullptr || signal == nullptr || method == nullptr) {qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",sender ? sender->metaObject()->className() : "(nullptr)",(signal && *signal) ? signal+1 : "(nullptr)",receiver ? receiver->metaObject()->className() : "(nullptr)",(method && *method) ? method+1 : "(nullptr)");return QMetaObject::Connection(0);}QByteArray tmp_signal_name;
//验证信号宏指令(SIGNAL)的正确性if (!check_signal_macro(sender, signal, "connect", "bind"))return QMetaObject::Connection(0);const QMetaObject *smeta = sender->metaObject();const char *signal_arg = signal;++signal; //skip codeQArgumentTypeArray signalTypes;Q_ASSERT(QMetaObjectPrivate::get(smeta)->revision >= 7);QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
//获取信号相对索引int signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signalName, signalTypes.size(), signalTypes.constData());if (signal_index < 0) {// check for normalized signaturestmp_signal_name = QMetaObject::normalizedSignature(signal - 1);signal = tmp_signal_name.constData() + 1;signalTypes.clear();signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);smeta = sender->metaObject();signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signalName, signalTypes.size(), signalTypes.constData());}if (signal_index < 0) {err_method_notfound(sender, signal_arg, "connect");err_info_about_objects("connect", sender, receiver);return QMetaObject::Connection(0);}signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index);
//加上偏移量得到信号全局索引值(绝对索引)signal_index += QMetaObjectPrivate::signalOffset(smeta);QByteArray tmp_method_name;int membcode = extract_code(method);
//检查槽函数代码是否有效if (!check_method_code(membcode, receiver, method, "connect"))return QMetaObject::Connection(0);const char *method_arg = method;++method; // skip codeQArgumentTypeArray methodTypes;QByteArray methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);const QMetaObject *rmeta = receiver->metaObject();int method_index_relative = -1;Q_ASSERT(QMetaObjectPrivate::get(rmeta)->revision >= 7);
//获取槽函数(或信号,信号连接信号时)相对索引switch (membcode) {case QSLOT_CODE:method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, methodName, methodTypes.size(), methodTypes.constData());break;case QSIGNAL_CODE:method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, methodName, methodTypes.size(), methodTypes.constData());break;}if (method_index_relative < 0) {// check for normalized methodstmp_method_name = QMetaObject::normalizedSignature(method);method = tmp_method_name.constData();methodTypes.clear();methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);// rmeta may have been modified abovermeta = receiver->metaObject();switch (membcode) {case QSLOT_CODE:method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, methodName, methodTypes.size(), methodTypes.constData());break;case QSIGNAL_CODE:method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, methodName, methodTypes.size(), methodTypes.constData());break;}}if (method_index_relative < 0) {err_method_notfound(receiver, method_arg, "connect");err_info_about_objects("connect", sender, receiver);return QMetaObject::Connection(0);}
//检查连接信号与槽时的参数是否匹配。if (!QMetaObjectPrivate::checkConnectArgs(signalTypes.size(), signalTypes.constData(),methodTypes.size(), methodTypes.constData())) {qWarning("QObject::connect: Incompatible sender/receiver arguments""\n        %s::%s --> %s::%s",sender->metaObject()->className(), signal,receiver->metaObject()->className(), method);return QMetaObject::Connection(0);}int *types = 0;
//检查连接方式if ((type == Qt::QueuedConnection)&& !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) {return QMetaObject::Connection(0);}#ifndef QT_NO_DEBUGQMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);QMetaMethod rmethod = rmeta->method(method_index_relative + rmeta->methodOffset());check_and_warn_compat(smeta, smethod, rmeta, rmethod);
#endif
//调用QMetaObjectPrivate::connectQMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));return handle;
}
2、QMetaObjectPrivate::connect

QMetaObjectPrivate::connectsignalmethod的信息存储到信号槽容器中,也就是将二者连接起来。

  1. 获取method的偏移值。
  2. 连接类型为Qt::UniqueConnection时检查是否已经连接,若是直接返回
  3. 创建Connection类c,填充数据(包括发送者、信号索引、接收者、method相对索引和绝对索引、连接类型、参数类型、调用函数),一个connection对象对应一个connect()函数的连接,将该对象
  4. 存储到connectionList
  5. 通知发送者,它的信号已经被连接到一个槽函数上。
QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender,int signal_index, const QMetaObject *smeta,const QObject *receiver, int method_index,const QMetaObject *rmeta, int type, int *types)
{
//Const_cast 变成可写QObject *s = const_cast<QObject *>(sender);QObject *r = const_cast<QObject *>(receiver);int method_offset = rmeta ? rmeta->methodOffset() : 0;Q_ASSERT(!rmeta || QMetaObjectPrivate::get(rmeta)->revision >= 6);QObjectPrivate::StaticMetaCallFunction callFunction = rmeta ? rmeta->d.static_metacall : nullptr;
//确保在多线程环境下,信号和槽的连接操作是线程安全的QOrderedMutexLocker locker(signalSlotLock(sender),signalSlotLock(receiver));
//type为Qt::UniqueConnection方式时排重QObjectPrivate::ConnectionData *scd  = QObjectPrivate::get(s)->connections.loadRelaxed();if (type & Qt::UniqueConnection && scd) {if (scd->signalVectorCount() > signal_index) {//一个信号对应一个signalVector中的元素,是connectionlist类,所以一个信号对应一个connectionlistconst QObjectPrivate::Connection *c2 = scd->signalVector.loadRelaxed()->at(signal_index).first.loadRelaxed();int method_index_absolute = method_index + method_offset;while (c2) {//已经存在就不建立连接if (!c2->isSlotObject && c2->receiver.loadRelaxed() == receiver && c2->method() == method_index_absolute)return nullptr;c2 = c2->nextConnectionList.loadRelaxed();}}type &= Qt::UniqueConnection - 1;}
//创建Connection类c,填充数据,一个connection对象对应一个connect连接std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};c->sender = s;c->signal_index = signal_index;c->receiver.storeRelaxed(r);QThreadData *td = r->d_func()->threadData;td->ref();c->receiverThreadData.storeRelaxed(td);c->method_relative = method_index;c->method_offset = method_offset;c->connectionType = type;c->isSlotObject = false;c->argumentTypes.storeRelaxed(types);c->callFunction = callFunction;
//将创建的connection存储到connectionLists(多个connection的qvector),get返回发送者的私有类QObjectPrivate::get(s)->addConnection(signal_index, c.get());locker.unlock();
//通知发送者,它的信号已经被连接到一个槽函数上QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);if (smethod.isValid())s->connectNotify(smethod);return c.release();
}
3、信号槽容器Conntion、ConnectionList、SignalVector、ConnectionData

Connection ->ConnectionList ->SignalVector ->ConnectionData,这四个结构体都定义在QObjectPrivate类中。

  • ConnectionData是每个QObject对象中的成员connections的类型,信号所在对象中的connections存储了该对象所有信号和信号连接的槽函数的信息。
  • SignalVectorConnectionData中的成员signalVector的类型,通过信号索引得到一个信号对应的ConnectionList
  • ConnectList是Qt中用于管理信号和槽连接的数据结构,它跟踪连接信息并在信号发射时找到对应的槽函数进行调用,ConnectionList与 一 一 信号对应。
  • ConnectList中的每个条目Conntion都表示一个连接,包含信号发送者、信号索引、槽接收者和槽索引等信息,Conntionconnect() 一 一 对应。

connect()中通过ObjectPrivate::get(s)->addConnection(signal_index, c.get())将新建的connection添加到信号槽容器connections中,首先得到QObjectPrivate成员ConnectionData对象connections,通过信号索引得到ConnectionData中的SignalVector成员signalVector中的信号对应的ConnectionList,将connection添加ConnectionList的结尾。

struct Connection : public ConnectionOrSignalVector
{Connection **prev;QAtomicPointer<Connection> nextConnectionList;Connection *prevConnectionList;QObject *sender;QAtomicPointer<QObject> receiver;//.....
};

结构体ConnectionList 是单向链表

// ConnectionList is a singly-linked liststruct ConnectionList {QAtomicPointer<Connection> first;QAtomicPointer<Connection> last;};
struct SignalVector : public ConnectionOrSignalVector 
{quintptr allocated;// ConnectionList signals[]ConnectionList &at(int i{//............}const ConnectionList &at(int i) const{//........................}int count() const { return static_cast<int>(allocated); }
};
 struct ConnectionData {// the id below is used to avoid activating new connections. When the object gets// deleted it's set to 0, so that signal emission stopsQAtomicInteger<uint> currentConnectionId;QAtomicInt ref;QAtomicPointer<SignalVector> signalVector;Connection *senders = nullptr;Sender *currentSender = nullptr;   // object currently activating the objectQAtomicPointer<Connection> orphaned;~ConnectionData(){//......}// must be called on the senders connection data// assumes the senders and receivers lock are heldvoid removeConnection(Connection *c);void cleanOrphanedConnections(QObject *sender) {///.....}void cleanOrphanedConnectionsImpl(QObject *sender);ConnectionList &connectionsForSignal(int signal) {///.....}void resizeSignalVector(uint size) {///.....}int signalVectorCount() const {//...}static void deleteOrphaned(ConnectionOrSignalVector *c);};

二、函数指针链接(qt5后)

编译时检查,拼写错误编译不通过;使用函数指针作为槽函数参数

 //connect to a function pointer  (not a member)template <typename Func1, typename Func2>static inline typename std::enable_if<int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0 &&!QtPrivate::FunctionPointer<Func2>::IsPointerToMemberFunction, QMetaObject::Connection>::typeconnect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const QObject *context, Func2 slot,Qt::ConnectionType type = Qt::AutoConnection)

三、Lamba表达式

  • 编译时检查
  • 不支持信号重载,支持槽函数重载
  • 默认directConnection

触发

在定义信号后,MOC编译器会在moc文件中定义该信号的信号函数。

// SIGNAL 0
void QDBusAdaptorConnector::relaySignal(QObject * _t1, const QMetaObject * _t2, int _t3, const QVariantList & _t4)
{
//----------_a是返回值和参数--------------------void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)), const_cast<void*>(reinterpret_cast<const void*>(&_t2)), const_cast<void*>(reinterpret_cast<const void*>(&_t3)), const_cast<void*>(reinterpret_cast<const void*>(&_t4)) };QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

当在程序中发射一个信号,是通过emit该信号实现的,emit是一个宏,定义为空白。

# define emit

发射信号实际上是就是调用这个信号函数,在这个函数中调用activate()来触发连接的槽函数。

void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,void **argv)
{//-------------由相对索引+偏移量得到绝对索引-------------------------int signal_index = local_signal_index + QMetaObjectPrivate::signalOffset(m);if (Q_UNLIKELY(qt_signal_spy_callback_set.loadRelaxed()))doActivate<true>(sender, signal_index, argv);elsedoActivate<false>(sender, signal_index, argv);
}

在得到绝对索引后,调用doActivate()

  1. 信号所在对象是否为阻塞状态,若是直接返回。
  2. 检查是否存在信号槽连接,没有返回。
  3. 获取信号所在对象中该信号对应的connection容器connectList
  4. 获取当前线程,并判断发送线程和信号所在对象的线程是否是同一线程。
  5. 遍历connectionlist,依次触发该信号所有链接的槽函数。

在嵌套的两个doWhile循环中遍历connectionlist,根据发送线程和接收者线程的关系进行不同的操作:

  • 如果是自动连接而且发送线程和接收者线程不一样,或者是队列链接,则使用queued_activated()post一个新建的QMetaCallEvent调用事件到接收者线程的事件队列。
  • 如果是阻塞队列连接,当发送线程和接收者在同一个线程警告检测到死锁,否则新建一个QMetaCallEvent调用事件,通过postEvent函数post到接收者线程的事件队列中。
  • 如果不是以上两种情况,即发送线程和接收者线程为同一线程,直接调用槽函数。
template <bool callbacks_enabled>
void doActivate(QObject *sender, int signal_index, void **argv)
{QObjectPrivate *sp = QObjectPrivate::get(sender);
//------------判断是否为阻塞状态--------------------------if (sp->blockSig)return;Q_TRACE_SCOPE(QMetaObject_activate, sender, signal_index);
//----------------检查是否存在信号槽链接--------------------------------if (sp->isDeclarativeSignalConnected(signal_index)&& QAbstractDeclarativeData::signalEmitted) {Q_TRACE_SCOPE(QMetaObject_activate_declarative_signal, sender, signal_index);QAbstractDeclarativeData::signalEmitted(sp->declarativeData, sender,signal_index, argv);}const QSignalSpyCallbackSet *signal_spy_set = callbacks_enabled ? qt_signal_spy_callback_set.loadAcquire() : nullptr;void *empty_argv[] = { nullptr };if (!argv)argv = empty_argv;if (!sp->maybeSignalConnected(signal_index)) {// The possible declarative connection is done, and nothing else is connectedif (callbacks_enabled && signal_spy_set->signal_begin_callback != nullptr)signal_spy_set->signal_begin_callback(sender, signal_index, argv);if (callbacks_enabled && signal_spy_set->signal_end_callback != nullptr)signal_spy_set->signal_end_callback(sender, signal_index);return;}if (callbacks_enabled && signal_spy_set->signal_begin_callback != nullptr)signal_spy_set->signal_begin_callback(sender, signal_index, argv);//-------获取信号所在对象中该信号对应的connection容器connectList------------------bool senderDeleted = false;{Q_ASSERT(sp->connections.loadAcquire());QObjectPrivate::ConnectionDataPointer connections(sp->connections.loadRelaxed());QObjectPrivate::SignalVector *signalVector = connections->signalVector.loadRelaxed();const QObjectPrivate::ConnectionList *list;if (signal_index < signalVector->count())list = &signalVector->at(signal_index);elselist = &signalVector->at(-1);
//-----------------------------------获取线程id-------------------------------Qt::HANDLE currentThreadId = QThread::currentThreadId();//获取当前线程,并判断是否是发送者的线程bool inSenderThread = currentThreadId == QObjectPrivate::get(sender)->threadData->threadId.loadRelaxed();// We need to check against the highest connection id to ensure that signals added// during the signal emission are not emitted in this emission.
//-------------遍历connectionlist,依次触发该信号所有链接的槽函数-----------------uint highestConnectionId = connections->currentConnectionId.loadRelaxed();do {QObjectPrivate::Connection *c = list->first.loadRelaxed();if (!c)continue;do {//获取接收者线程数据QObject * const receiver = c->receiver.loadRelaxed();if (!receiver)continue;QThreadData *td = c->receiverThreadData.loadRelaxed();if (!td)continue;bool receiverInSameThread;if (inSenderThread) {receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();} else {// need to lock before reading the threadId, because moveToThread() could interfere//信号所在线程和发送者不在一个线程,在获取connection中的接收者线程时上锁QMutexLocker lock(signalSlotLock(receiver));receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();}//--------------如果是多线程连接,即队列和阻塞队列连接-------------------------// determine if this connection should be sent immediately or// put into the event queue//如果是自动连接而且发送线程和接收者线程不一样,或者是队列链接,使用queued_activated调用槽函数if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)|| (c->connectionType == Qt::QueuedConnection)) {queued_activate(sender, signal_index, c, argv);continue;
#if QT_CONFIG(thread)//如果是阻塞队列连接} else if (c->connectionType == Qt::BlockingQueuedConnection) {//当发送的线程和接收者在同一个线程,警告:当激活一个阻塞队列连接时检测到死锁if (receiverInSameThread) {qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: ""Sender is %s(%p), receiver is %s(%p)",sender->metaObject()->className(), sender,receiver->metaObject()->className(), receiver);}//postEvent发送一个qmetacallevent事件QSemaphore semaphore;{QBasicMutexLocker locker(signalSlotLock(sender));if (!c->receiver.loadAcquire())continue;QMetaCallEvent *ev = c->isSlotObject ?new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore) :new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction,sender, signal_index, argv, &semaphore);QCoreApplication::postEvent(receiver, ev);}semaphore.acquire();continue;
#endif}
//-----------------------如果是单线程连接-------------------------------------QObjectPrivate::Sender senderData(receiverInSameThread ? receiver : nullptr, sender, signal_index);if (c->isSlotObject) {c->slotObj->ref();struct Deleter {void operator()(QtPrivate::QSlotObjectBase *slot) const {if (slot) slot->destroyIfLastRef();}};const std::unique_ptr<QtPrivate::QSlotObjectBase, Deleter> obj{c->slotObj};{Q_TRACE_SCOPE(QMetaObject_activate_slot_functor, obj.get());obj->call(receiver, argv);}} else if (c->callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {//we compare the vtable to make sure we are not in the destructor of the object.const int method_relative = c->method_relative;const auto callFunction = c->callFunction;const int methodIndex = (Q_HAS_TRACEPOINTS || callbacks_enabled) ? c->method() : 0;if (callbacks_enabled && signal_spy_set->slot_begin_callback != nullptr)signal_spy_set->slot_begin_callback(receiver, methodIndex, argv);{Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, methodIndex);callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv);}if (callbacks_enabled && signal_spy_set->slot_end_callback != nullptr)signal_spy_set->slot_end_callback(receiver, methodIndex);} else {const int method = c->method_relative + c->method_offset;if (callbacks_enabled && signal_spy_set->slot_begin_callback != nullptr) {signal_spy_set->slot_begin_callback(receiver, method, argv);}{Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, method);QMetaObject::metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv);}if (callbacks_enabled && signal_spy_set->slot_end_callback != nullptr)signal_spy_set->slot_end_callback(receiver, method);}} while ((c = c->nextConnectionList.loadRelaxed()) != nullptr && c->id <= highestConnectionId);} while (list != &signalVector->at(-1) &&//start over for all signals;((list = &signalVector->at(-1)), true));if (connections->currentConnectionId.loadRelaxed() == 0)senderDeleted = true;}if (!senderDeleted) {sp->connections.loadRelaxed()->cleanOrphanedConnections(sender);if (callbacks_enabled && signal_spy_set->signal_end_callback != nullptr)signal_spy_set->signal_end_callback(sender, signal_index);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/216215.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

14-Kafka-Day02

第 4 章 Kafka Broker 4.1 Kafka Broker 工作流程 4.1.1 Zookeeper 存储的 Kafka 信息 &#xff08;1&#xff09;启动 Zookeeper 客户端。 bin/zkCli.sh 因为你在配置kafka的时候指定了它的名字。 &#xff08;2&#xff09;通过 ls 命令可以查看 kafka 相关信息。 [zk: …

超越边界:Mistral 7B挑战AI新标准,全面超越Llama 2 13B

引言 在人工智能领域&#xff0c;模型的性能一直是衡量其价值和应用潜力的关键指标。近日&#xff0c;一个新的里程碑被设立&#xff1a;Mistral AI发布了其最新模型Mistral 7B&#xff0c;它在众多基准测试中全面超越了Llama 2 13B模型&#xff0c;标志着AI技术的一个重大进步…

深兰科技“汉境”入选2023年湖北省人工智能十大优秀应用案例

11月18日&#xff0c;央视“专精特新制造强国”城市大会在湖北武汉召开。会上&#xff0c;正式发布了“湖北省工业互联网标识十大优秀应用案例”&#xff0c;由深兰科技(武汉)股份有限公司基于AIGC多模态融合大模型技术开发打造的江汉路步行街元宇宙场景应用——汉境&#xff0…

Java异步编程之利器:Guava异步编程实践

第1章&#xff1a;引言 - 为什么要用Guava进行异步编程&#xff1f; 大家好&#xff0c;我是小黑&#xff01;今天咱们要聊的是Guava在异步编程中的应用。首先&#xff0c;让我们搞清楚为什么要用Guava来处理异步任务。在Java的世界里&#xff0c;异步编程是个老话题了&#x…

漏洞复现--速达进存销管理系统任意文件上传

免责声明&#xff1a; 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直…

vue项目 treeselect校验不生效

使用treeselect时&#xff0c;el-form表单检验rules不生效&#xff0c;使用blur与change都无效&#xff0c;我的解决方法代码如下&#xff1a; 在treeselect标签里使用select方法&#xff0c;该方法为选择一个项后发出&#xff0c;在每次选择组织的时候都进行unitId的校验。 …

KaiwuDB 获评信通院 2023 大数据“星河”标杆案例

12月6日&#xff0c;由中国信息通信研究院、中国通信标准化协会大数据技术标准推进委员会(CCSA TC601) 共同组织的 2023 大数据“星河(Galaxy)”案例评选结果正式公示&#xff0c;“基于 KaiwuDB 的台区云储能示范项目”历经多环节严苛评审&#xff0c;从累计 706 份申报项目中…

el-table表格出现滚动条的时候,内容和表头对不齐

1&#xff0c;在获取表格数据完成后的位置加 this.$nextTick(() > {this.$refs.templateData.doLayout(); })2,要记得在上加上ref <el-table ref"templateData" ></el-table>加完之后的

IDEA使用小技巧

常用的基本设置 界面字体 File | Settings | Appearance & Behavior | Appearance 编辑区字体 File | Settings | Editor | Color Scheme | Color Scheme Font Use color scheme font instead of the default 控制台字体 File | Settings | Editor | Color Scheme | Conso…

免费安装office流程(官方自动批量激活)

全程不需要第三方软件&#xff0c;所有用到的工具都是微软官方的&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 基于KMS的 GVLK&#xff1a;https://learn.microsoft.com/zh-cn/deployoffice/vlactivation/gvlks 首先我们需要去下载 office 软件部署工具&a…

神经网络是如何工作的? | 京东云技术团队

作为一名程序员&#xff0c;我们习惯于去了解所使用工具、中间件的底层原理&#xff0c;本文则旨在帮助大家了解AI模型的底层机制&#xff0c;让大家在学习或应用各种大模型时更加得心应手&#xff0c;更加适合没有AI基础的小伙伴们。 一、GPT与神经网络的关系 GPT想必大家已…

boost::graph学习

boost::graph API简单小结 boost::graph是boost为图算法提供的API&#xff0c;简单易用。 API说明 boost::add_vertex 创建一个顶点。 boost::add_edge 创建一条边。 boost::edges 获取所有的边。 boost::vertices 获取所有的顶点。 graph.operator[vertex_descriptor] 获…

CCF编程能力等级认证GESP—C++1级—20230923

CCF编程能力等级认证GESP—C1级—20230923 单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09;判断题&#xff08;每题 2 分&#xff0c;共 20 分&#xff09;编程题 (每题 25 分&#xff0c;共 50 分)买⽂具⼩明的幸运数 答案及解析单选题判断题编程题1编程题2 单选…

MFC CLXHHandleEngine动态库-自定义设置对话框使用

实现的效果如下所示&#xff1a; void CSampleDlg::OnBnClickedButton2() { // TODO: 在此添加控件通知处理程序代码 CSgxMemDialog dlg(180, 100); dlg.SetEnable(true); dlg.SetWindowTitle(_T("自定义对话框")); dlg.AddStatic(1000, //控件资源…

基于ssm亿互游在线平台设计与开发论文

摘 要 随着旅游业的迅速发展&#xff0c;传统的旅游资讯查询方式&#xff0c;已经无法满足用户需求&#xff0c;因此&#xff0c;结合计算机技术的优势和普及&#xff0c;特开发了本亿互游在线平台。 本文研究的亿互游在线平台基于SSM框架&#xff0c;采用JSP技术、Java语言和…

HTML行内元素和块级元素的区别? 分别有哪些?

目录 一、行内元素和块级元素的区别二、行内元素和块级元素分别有哪些1、行内元素2、块级元素 一、行内元素和块级元素的区别 1、行内元素不会占据整行&#xff0c;在一条直线上排列&#xff0c;都是同一行&#xff0c;水平方向排列&#xff1b;    2、块级元素可以包含行内…

Android---Kotlin 学习001

Kotlin 的诞生 2011年&#xff0c;JetBrains 宣布开发 Kotlin 编程语言&#xff0c;这门新语言可以用来编写在 Java 虚拟机上运行的代码&#xff0c;是 Java 和 Scale 语言之外的又一选择。2017年&#xff0c;Google 在赢得与 Oracle 的诉讼一年后&#xff0c;Google 宣布 Ko…

仿短视频风格的自适应苹果CMS模板源码

这是一款仿短视频风格的自适应苹果CMS模板源码&#xff0c;设计简洁&#xff0c;适合用于搭建个人视频网站或者短视频分享平台。模板支持响应式布局&#xff0c;演示地 址 runruncode.com/yingshimanhau/19650.html 适配各种屏幕尺寸&#xff0c;功能丰富&#xff0c;用户体验良…

H.265视频压缩编码标准

H.265&#xff08;High Efficiency Video Coding&#xff0c;也称为HEVC&#xff09;是一种新一代视频压缩编码标准&#xff0c;被视为H.264的继任者。它引入了许多创新的技术和算法&#xff0c;以提供更高质量的视频压缩效果。在本文中&#xff0c;我将详细介绍H.265的背景、特…

【Go自学版】03-即时通信系统2

4. 在线用户查询 main.go | server.go | user.go // server.go type Server struct {IP stringPort int// 在线用户列表OnlineMap map[string]*UsermapLock sync.RWMutex// 消息广播Message chan string }// 创建server接口 func NewServer(ip string, port int) *Server…