上一节中我们讲了QObject是Qt中使用Meta-Object元对象模型或者说使用信号与槽机制,必须继承的根基类,一般面向对象语言都会有这么一个根基类,提供了语言的基础,那么Qt作为C++的扩展库,QObject作为Qt的根类,为我们提供了哪些功能呢?
在Qt的构造函数中,我们可以发现都带有一个QObject* parent=0的默认参数,这个parent就是用来指定父对象
QObject::QObject(QObject *parent) : d_ptr(new QObjectPrivate) { Q_D(QObject); d_ptr->q_ptr = this; d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current(); d->threadData->ref(); if (parent) { QT_TRY { if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData)) parent = 0; setParent(parent); } QT_CATCH(...) { d->threadData->deref(); QT_RETHROW; } } qt_addObject(this); if (Q_UNLIKELY(qtHookData[QHooks::AddQObject])) reinterpret_cast<QHooks::AddQObjectCallback>(qtHookData[QHooks::AddQObject])(this); } 123456789101112131415161718192021先看看d_ptr的定义
QScopedPointer<QObjectData> d_ptr; 12它是一个QObjectData的指针,在Qt源码中一般用d_ptr表示类的数据指针。
class Q_CORE_EXPORT QObjectData { public: virtual ~QObjectData() = 0; QObject *q_ptr; QObject *parent; QObjectList children; uint isWidget : 1; uint blockSig : 1; uint wasDeleted : 1; uint isDeletingChildren : 1; uint sendChildEvents : 1; uint receiveChildEvents : 1; uint isWindow : 1; //for QWindow uint unused : 25; int postedEvents; QDynamicMetaObjectData *metaObject; QMetaObject *dynamicMetaObject() const; }; 12345678910111213141516171819QObjectData 定义了一个q_ptr指针指回QObject,parent 指向父对象,children保存子对象,还定义了一些标志位,当然还有我们上节的QMetaObject 类元信息。
在构造函数有这么一句
d_ptr(new QObjectPrivate) 12那么QObjectPrivate是什么,既然它能赋值给d_ptr,那应该是QObjectData的派生类,转到定义看看。
class Q_CORE_EXPORT QObjectPrivate : public QObjectData { Q_DECLARE_PUBLIC(QObject) public: struct ExtraData { ExtraData() {} #ifndef QT_NO_USERDATA QVector<QObjectUserData *> userData; #endif QList<QByteArray> propertyNames; QVector<QVariant> propertyValues; QVector<int> runningTimers; QList<QPointer<QObject> > eventFilters; QString objectName; }; //... } 1234567891011121314151617181920果然,它就是继承自QObjectData,然后带了些额外的数据
#define Q_DECLARE_PUBLIC(Class) \ inline Class* q_func() { return static_cast<Class *>(q_ptr); } \ inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \ friend class Class; 12345Q_DECLARE_PUBLIC宏声明了友元类,并通过q_func()方法返回q_ptr,即QObject类指针
在QObject的声明中也有如下一句:
Q_DECLARE_PRIVATE(QObject) 12看看Q_DECLARE_PRIVATE的定义
#define Q_DECLARE_PRIVATE(Class) \ inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \ inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \ friend class Class##Private; 12345在Qt的源码中到处可以见到这样的好基友,通过声明一个名字为ClassPrivate的友元类,用来保存私有数据,保证数据的封装性和隐秘性
继续看QObject的构造函数,又有这么一个宏Q_D,Qt还真是喜欢用宏定义,这样可以使代码简洁些
#define Q_D(Class) Class##Private * const d = d_func()就是定义一个d指向私有类,定义一个q指向本身。继续
d_ptr->q_ptr = this; 12d_ptr通过new QObjectPrivate,指向QObjectPrivate,那么d_ptr->q_ptr = this就是将QObjectPrivate中的q_ptr指回QObject
不要被绕晕了,其实很简单,一句话总结 QObject和QObjectPrivate互为友元类,QObject中new了一个QObjectPrivate,通过d_ptr保存QObjectPrivate的指针,而QObjectPrivate中通过q_ptr指回QObejct,真是一对好基友
继续
d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current(); d->threadData->ref(); if (parent) { QT_TRY { if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData)) parent = 0; setParent(parent); } QT_CATCH(...) { d->threadData->deref(); QT_RETHROW; } 123456789101112}
线程数据我们这里先不深究,大抵意思是比较父对象和当前对象是否是同一线程中创建,如果不是是会抛出异常的。这就是说Qt要求父对象和子对象必须在同一线程中创建,这是因为父对象后面既然负责子对象的销毁工作,如果是跨线程销毁,会带来毁灭性的灾害。 setParent(parent);就是赋值QObjectData中parent指针了,转到定义看最终调用如下
void QObjectPrivate::setParent_helper(QObject *o) { Q_Q(QObject); if (o == parent) return; if (parent) { QObjectPrivate *parentD = parent->d_func(); if (parentD->isDeletingChildren && wasDeleted && parentD->currentChildBeingDeleted == q) { // don't do anything since QObjectPrivate::deleteChildren() already // cleared our entry in parentD->children. } else { const int index = parentD->children.indexOf(q); if (parentD->isDeletingChildren) { parentD->children[index] = 0; } else { parentD->children.removeAt(index); if (sendChildEvents && parentD->receiveChildEvents) { QChildEvent e(QEvent::ChildRemoved, q); QCoreApplication::sendEvent(parent, &e); } } } } parent = o; if (parent) { // object hierarchies are constrained to a single thread if (threadData != parent->d_func()->threadData) { qWarning("QObject::setParent: Cannot set parent, new parent is in a different thread"); parent = 0; return; } parent->d_func()->children.append(q); if(sendChildEvents && parent->d_func()->receiveChildEvents) { if (!isWidget) { QChildEvent e(QEvent::ChildAdded, q); QCoreApplication::sendEvent(parent, &e); } } } if (!wasDeleted && !isDeletingChildren && declarativeData && QAbstractDeclarativeData::parentChanged) QAbstractDeclarativeData::parentChanged(declarativeData, q, o); } 12345678910111213141516171819202122232425262728293031323334353637383940414243一来赋值了parent,二来在parent的QObejectData中增加上该children,并且QCoreApplication::sendEvent给父对象发送了一个QChildEvent添加子类事件
继续是qt_addObject
// ### Qt >= 5.6, remove qt_add/removeObject extern "C" Q_CORE_EXPORT void qt_addObject(QObject *) {} extern "C" Q_CORE_EXPORT void qt_removeObject(QObject *) {} 1234567从定义和注释看,qt_addObject和qt_removeObject这两个函数只是一个空壳了,应该是以前版本的遗留物。
if (Q_UNLIKELY(qtHookData[QHooks::AddQObject])) reinterpret_cast<QHooks::AddQObjectCallback>(qtHookData[QHooks::AddQObject])(this); 123qtHookData是Qt预留的钩子回调函数指针数组,当new一个QObject对象时,会触发QHooks::AddQObject钩子回调函数
再看看QObject的析构函数
QObject::~QObject() { Q_D(QObject); d->wasDeleted = true; d->blockSig = 0; // unblock signals so we always emit destroyed() QtSharedPointer::ExternalRefCountData *sharedRefcount = d->sharedRefcount.load(); if (sharedRefcount) { if (sharedRefcount->strongref.load() > 0) { qWarning("QObject: shared QObject was deleted directly. The program is malformed and may crash."); // but continue deleting, it's too late to stop anyway } // indicate to all QWeakPointers that this QObject has now been deleted sharedRefcount->strongref.store(0); if (!sharedRefcount->weakref.deref()) delete sharedRefcount; } if (!d->isWidget && d->isSignalConnected(0)) { emit destroyed(this); } if (d->declarativeData) { if (static_cast<QAbstractDeclarativeDataImpl*>(d->declarativeData)->ownedByQml1) { if (QAbstractDeclarativeData::destroyed_qml1) QAbstractDeclarativeData::destroyed_qml1(d->declarativeData, this); } else { if (QAbstractDeclarativeData::destroyed) QAbstractDeclarativeData::destroyed(d->declarativeData, this); } } // set ref to zero to indicate that this object has been deleted if (d->currentSender != 0) d->currentSender->ref = 0; d->currentSender = 0; if (d->connectionLists || d->senders) { QMutex *signalSlotMutex = signalSlotLock(this); QMutexLocker locker(signalSlotMutex); // disconnect all receivers if (d->connectionLists) { ++d->connectionLists->inUse; int connectionListsCount = d->connectionLists->count(); for (int signal = -1; signal < connectionListsCount; ++signal) { QObjectPrivate::ConnectionList &connectionList = (*d->connectionLists)[signal]; while (QObjectPrivate::Connection *c = connectionList.first) { if (!c->receiver) { connectionList.first = c->nextConnectionList; c->deref(); continue; } QMutex *m = signalSlotLock(c->receiver); bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m); if (c->receiver) { *c->prev = c->next; if (c->next) c->next->prev = c->prev; } c->receiver = 0; if (needToUnlock) m->unlock(); connectionList.first = c->nextConnectionList; // The destroy operation must happen outside the lock if (c->isSlotObject) { c->isSlotObject = false; locker.unlock(); c->slotObj->destroyIfLastRef(); locker.relock(); } c->deref(); } } if (!--d->connectionLists->inUse) { delete d->connectionLists; } else { d->connectionLists->orphaned = true; } d->connectionLists = 0; } /* Disconnect all senders: * This loop basically just does * for (node = d->senders; node; node = node->next) { ... } * * We need to temporarily unlock the receiver mutex to destroy the functors or to lock the * sender's mutex. And when the mutex is released, node->next might be destroyed by another * thread. That's why we set node->prev to &node, that way, if node is destroyed, node will * be updated. */ QObjectPrivate::Connection *node = d->senders; while (node) { QObject *sender = node->sender; // Send disconnectNotify before removing the connection from sender's connection list. // This ensures any eventual destructor of sender will block on getting receiver's lock // and not finish until we release it. sender->disconnectNotify(QMetaObjectPrivate::signal(sender->metaObject(), node->signal_index)); QMutex *m = signalSlotLock(sender); node->prev = &node; bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m); //the node has maybe been removed while the mutex was unlocked in relock? if (!node || node->sender != sender) { // We hold the wrong mutex Q_ASSERT(needToUnlock); m->unlock(); continue; } node->receiver = 0; QObjectConnectionListVector *senderLists = sender->d_func()->connectionLists; if (senderLists) senderLists->dirty = true; QtPrivate::QSlotObjectBase *slotObj = Q_NULLPTR; if (node->isSlotObject) { slotObj = node->slotObj; node->isSlotObject = false; } node = node->next; if (needToUnlock) m->unlock(); if (slotObj) { if (node) node->prev = &node; locker.unlock(); slotObj->destroyIfLastRef(); locker.relock(); } } } if (!d->children.isEmpty()) d->deleteChildren(); qt_removeObject(this); if (Q_UNLIKELY(qtHookData[QHooks::RemoveQObject])) reinterpret_cast<QHooks::RemoveQObjectCallback>(qtHookData[QHooks::RemoveQObject])(this); if (d->parent) // remove it from parent object d->setParent_helper(0); } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150比较长,关键几处有: emit destroyed(this);发出销毁信号
int connectionListsCount = d->connectionLists->count(); for (int signal = -1; signal < connectionListsCount; ++signal) { QObjectPrivate::ConnectionList &connectionList = (*d->connectionLists)[signal]; while (QObjectPrivate::Connection *c = connectionList.first) { if (!c->receiver) { connectionList.first = c->nextConnectionList; c->deref(); continue; } //... } 1234567891011121314断开所有信号与槽的连接
if (!d->children.isEmpty()) d->deleteChildren(); 123删除子对象
if (Q_UNLIKELY(qtHookData[QHooks::RemoveQObject])) reinterpret_cast<QHooks::RemoveQObjectCallback>(qtHookData[QHooks::RemoveQObject])(this); 123触发移除对象的钩子回调函数
if (d->parent) // remove it from parent object d->setParent_helper(0); // setParent_helper中会执行如下语句 QChildEvent e(QEvent::ChildRemoved, q); QCoreApplication::sendEvent(parent, &e); 1234567从父对象的子对象列表中移除该对象,此时会向父对象发出一个移除子对象的事件
总结:在Qt的构造的析构函数中,就实现了对象树。每个对象会保留父对象指针和子对象列表,构造时设置父指针,并在父对象的子对象列表中添加该对象,析构时会删除所有子对象,并从父对象的子对象列表中移除该对象。
Qt的对象树很有用,在界面构造时,我们的子控件只需要指定了父控件,那么父控件销毁时会自动销毁子控件,而不用我们操心,所以在Qt的应用程序代码中你可以发现只有new,没有delete,这就是对象树提供的方便之处,妈妈再也不用担心内存泄漏问题了
信号与槽机制的根基通过QMetaObject提供,这在上一节已经讲了,通过signals,slots等宏声明定义信号、槽。QMetaObject中会记录这些信号与槽函数,并通过索引号查找,moc会自动添加上qt_metacall的实现,通过索引调用对应的函数。
这里主要看下connect和disconnect的实现。
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type) { if (sender == 0 || receiver == 0 || signal == 0 || method == 0) { qWarning("QObject::connect: Cannot connect %s::%s to %s::%s", sender ? sender->metaObject()->className() : "(null)", (signal && *signal) ? signal+1 : "(null)", receiver ? receiver->metaObject()->className() : "(null)", (method && *method) ? method+1 : "(null)"); return QMetaObject::Connection(0); } QByteArray tmp_signal_name; if (!check_signal_macro(sender, signal, "connect", "bind")) return QMetaObject::Connection(0); const QMetaObject *smeta = sender->metaObject(); const char *signal_arg = signal; ++signal; //skip code QArgumentTypeArray 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 signatures tmp_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 code QArgumentTypeArray 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 methods tmp_method_name = QMetaObject::normalizedSignature(method); method = tmp_method_name.constData(); methodTypes.clear(); methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes); // rmeta may have been modified above rmeta = 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_DEBUG QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index); QMetaMethod rmethod = rmeta->method(method_index_relative + rmeta->methodOffset()); check_and_warn_compat(smeta, smethod, rmeta, rmethod); #endif QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect( sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types)); return handle; } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117通过SIGNAL和SLOT宏传入的是函数签名字符串,所以会先调用QMetaObjectPrivate::decodeMethodSignature去解析函数签名,再调用QMetaObjectPrivate::indexOfSlotRelative去获取她们在QMetaObject中记录的索引 最终调用QMetaObjectPrivate::connect
继续跟踪
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) { 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 : 0; QOrderedMutexLocker locker(signalSlotLock(sender), signalSlotLock(receiver)); if (type & Qt::UniqueConnection) { QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists; if (connectionLists && connectionLists->count() > signal_index) { const QObjectPrivate::Connection *c2 = (*connectionLists)[signal_index].first; int method_index_absolute = method_index + method_offset; while (c2) { if (!c2->isSlotObject && c2->receiver == receiver && c2->method() == method_index_absolute) return 0; c2 = c2->nextConnectionList; } } type &= Qt::UniqueConnection - 1; } QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection); c->sender = s; c->signal_index = signal_index; c->receiver = r; c->method_relative = method_index; c->method_offset = method_offset; c->connectionType = type; c->isSlotObject = false; c->argumentTypes.store(types); c->nextConnectionList = 0; c->callFunction = callFunction; QObjectPrivate::get(s)->addConnection(signal_index, c.data()); locker.unlock(); QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index); if (smethod.isValid()) s->connectNotify(smethod); return c.take(); } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354应该明白了,new 一个QObjectPrivate::Connection,里面保存了发送者,信号索引,接受者,方法索引,连接类型等信息,再终调用QObjectPrivate::get(s)->addConnection(signal_index, c.data()); 当然还调用了一个连接通知函数connectNotify
void QObjectPrivate::addConnection(int signal, Connection *c) { Q_ASSERT(c->sender == q_ptr); if (!connectionLists) connectionLists = new QObjectConnectionListVector(); if (signal >= connectionLists->count()) connectionLists->resize(signal + 1);
ConnectionList &connectionList = (*connectionLists)[signal]; if (connectionList.last) { connectionList.last->nextConnectionList = c; } else { connectionList.first = c; } connectionList.last = c; cleanConnectionLists(); c->prev = &(QObjectPrivate::get(c->receiver)->senders); c->next = *c->prev; *c->prev = c; if (c->next) c->next->prev = &c->next; if (signal < 0) { connectedSignals[0] = connectedSignals[1] = ~0; } else if (signal < (int)sizeof(connectedSignals) * 8) { connectedSignals[signal >> 5] |= (1 << (signal & 0x1f)); } 12345678910111213141516171819202122}
真相大白了,new了一个QObjectConnectionListVector连接列表向量,ConnectionList &connectionList = (*connectionLists)[signal];通过信号索引找到连接列表,也就是说一个信号可以连接多个接收,连接列表向量中保存所有信号的连接列表。
disconnect流程和connect大抵类似: 解析函数签名->提取对应索引->调用QMetaObjectPrivate::disconnect->从连接列表向量中提取对应信号的连接列表->从连接列表中将ref连接次数-1,如果为0则移出列表->调用断连通知函数disconnectNotify
在Meta-Object Model中我们已经讲过Qt提供了动态的添加属性的方法,在QObject中给出的接口就是:
bool setProperty(const char *name, const QVariant &value); QVariant property(const char *name) const; QList<QByteArray> dynamicPropertyNames() const; 1234QObject中提供了如下几个事件处理接口:
void installEventFilter(QObject *filterObj); void removeEventFilter(QObject *obj); virtual bool event(QEvent *event); virtual bool eventFilter(QObject *watched, QEvent *event); 12345 bool QObject::event(QEvent *e) { switch (e->type()) { case QEvent::Timer: timerEvent((QTimerEvent*)e); break; case QEvent::ChildAdded: case QEvent::ChildPolished: case QEvent::ChildRemoved: childEvent((QChildEvent*)e); break; case QEvent::DeferredDelete: qDeleteInEventHandler(this); break; case QEvent::MetaCall: { QMetaCallEvent *mce = static_cast<QMetaCallEvent*>(e); QConnectionSenderSwitcher sw(this, const_cast<QObject*>(mce->sender()), mce->signalId()); mce->placeMetaCall(this); break; } case QEvent::ThreadChange: { Q_D(QObject); QThreadData *threadData = d->threadData; QAbstractEventDispatcher *eventDispatcher = threadData->eventDispatcher.load(); if (eventDispatcher) { QList<QAbstractEventDispatcher::TimerInfo> timers = eventDispatcher->registeredTimers(this); if (!timers.isEmpty()) { // do not to release our timer ids back to the pool (since the timer ids are moving to a new thread). eventDispatcher->unregisterTimers(this); QMetaObject::invokeMethod(this, "_q_reregisterTimers", Qt::QueuedConnection, Q_ARG(void*, (new QList<QAbstractEventDispatcher::TimerInfo>(timers)))); } } break; } default: if (e->type() >= QEvent::User) { customEvent(e); break; } return false; } return true; } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152event中进行了一些事件的类别判断,然后分发到各个细分函数进行处理,像timerEvent,childEvent等,当然支持自定义事件 QEvent::User+,分发到customEvent中处理。
QObject提供了添加自定义数据的接口
static uint registerUserData(); void setUserData(uint id, QObjectUserData* data); QObjectUserData* userData(uint id) const; 1234QObjectUserData是一个空类,我们继承这个类,通过registerUserData注册一个用户数据,然后setUserData设置用户数据即可,通过userData就可以提取这个用户数据了
QObject提供了对象树,信号与槽机制,属性系统,事件系统,支持添加自定义数据。