做一下QObject的相关笔记,准备来一个系统性的学习。
以下内容全部是个人理解,有谬误的地方欢迎大佬指出!
QObject的成员变量
首先,我们先看一下QObject的大小:
qDebug()<<sizeof(QObject);
结果输出为8,我们打开QObject的源码,可以发现这8个字节是类的虚表指针和一个指针成员变量
QScopedPointer<QObjectData> d_ptr;
点开定义发现QScopedPointer是一个模板类,内部只有一个T*的指针,所以这个d_ptr其实就是一个QObjectData的指针,定义如下:
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;
};
这里面储存了QObject的一些信息,包括父节点指针,子节点指针,是否为Widget等。
这样,QObject中包含了所有的方法,QObjectDate里存储数据信息,这就是QT的句柄实例设计模式,QObject是句柄类,QObjectDate则是实例类。所以,QObject的派生类可以说有两个基类,一个是QObject,另一个是QObjectDate。由此展开了d_ptr和p_ptr,这个以后在复习d指针的时候再展开。
这样设计有什么好处,具体可以参考Pimpl机制,可以隐藏信息,降低耦合。
接下来,我们看构造函数:
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;
}
}
#if QT_VERSION < 0x60000
qt_addObject(this);
#endif
if (Q_UNLIKELY(qtHookData[QHooks::AddQObject]))
reinterpret_cast<QHooks::AddQObjectCallback>(qtHookData[QHooks::AddQObject])(this);
}
我们注意到,调用构造函数的时候,我们new了一个QObjectPrivate给d_ptr赋值,翻看源码可以发现,QObjectPrivate继承了QObjectDate,并且提供了一些线程以及信号槽相关的具体实现,其实,所有的QObject的派生类都会在构造函数中new一个相应的private类,比如QPushButton:
QPushButton::QPushButton(QWidget *parent)
: QAbstractButton(*new QPushButtonPrivate, parent)
{
Q_D(QPushButton);
d->init();
}
这个QpushButtonPrivate的继承关系里就有一环继承了QObjectPrivate。这里面的Q_D展开来就是QPushButtonPrivate* const d = d_func。
至于QObjectPrivate如何进行信号槽的实现,留到以后再记录。
对了,值得一提的是,QObject禁用了赋值和拷贝构造函数,因为QObject有唯一的名字,而且处于树型结构中,如果可以复制,无疑会增添很多麻烦,更不用说如何处理这些已经连接上的信号槽。
继续说回QObject的构造函数。
Q_D(QObject)的作用可以参考下面的宏的解释,其中变量d的类型为QObjectPrivate*,我们初始化了线程threadDate。
QObject的宏定义
1,Q_OBJECT
以后分析元对象系统的时候再梳理
2,Q_PROPERTY
QT属性,留待以后分析
3,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;
在这个宏中,创建了两个内联函数,将d_ptr转换成ClassPrivate*返回,帮我们获取指向实例的指针,而且还声明了友元类,可以让我们省去了访问权限的问题。值得一提的是,同名同参的函数如果返回值的const属性不同,也可以实现重载。
4,Q_DECLARE_PUBLIC
#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;
同上面的宏类似,帮我们获取指向句柄的指针,在QObjectPrivate中使用。
5,Q_D
#define Q_D(Class) Class##Private * const d = d_func()
定义了一个变量d指向了实例指针
6,Q_Q
#define Q_Q(Class) Class * const q = q_func()
定义了变量q指向了句柄指针
QObject的接口
1, QThread *thread() const;
返回对象所在的线程。
2,void moveToThread(QThread *thread);
if (d->threadData->thread == targetThread) {
// object is already in this thread
return;
}
这个很好理解,如果自身线程和目标线程相同,就什么都不做
if (d->parent != 0) {
qWarning("QObject::moveToThread: Cannot move objects with a parent");
return;
}
被move的对象不能有父对象,不然无法move成功。
if (d->isWidget) {
qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread");
return;
}
if (d->isWidget) {
qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread");
return;
}
QWidget只能在主线程。
QThreadData *currentData = QThreadData::current();
QThreadData *targetData = targetThread ? QThreadData::get2(targetThread) : Q_NULLPTR;
if (d->threadData->thread == 0 && currentData == targetData) {
// one exception to the rule: we allow moving objects with no thread affinity to the current thread
currentData = d->threadData;
} else if (d->threadData != currentData) {
qWarning("QObject::moveToThread: Current thread (%p) is not the object's thread (%p).\n"
"Cannot move to target thread (%p)\n",
currentData->thread.load(), d->threadData->thread.load(), targetData ? targetData->thread.load() : Q_NULLPTR);
return;
}
如果当前线程不是对象所在的线程,那么我们也无法将对象移动到目标线程,除非对象与当前线程没有关联。
// prepare to move
d->moveToThread_helper();
if (!targetData)
targetData = new QThreadData(0);
QOrderedMutexLocker locker(¤tData->postEventList.mutex,
&targetData->postEventList.mutex);
// keep currentData alive (since we've got it locked)
currentData->ref();
// move the object
d_func()->setThreadData_helper(currentData, targetData);
locker.unlock();
// now currentData can commit suicide if it wants to
currentData->deref();
然后我们将对象移动到了目标线程。
3, static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,const QObject *receiver, const QMetaMethod &method,Qt::ConnectionType type = Qt::AutoConnection);
if (sender == 0
|| receiver == 0
|| signal.methodType() != QMetaMethod::Signal
|| method.methodType() == QMetaMethod::Constructor) {
qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
sender ? sender->metaObject()->className() : "(null)",
signal.methodSignature().constData(),
receiver ? receiver->metaObject()->className() : "(null)",
method.methodSignature().constData() );
return QMetaObject::Connection(0);
}
信号的发送者和接收者都不能为空,而且第二个参数signal的类型必须为Signal,第四个参数method不能为构造函数,不然直接返回。
int signal_index;
int method_index;
{
int dummy;
QMetaObjectPrivate::memberIndexes(sender, signal, &signal_index, &dummy);
QMetaObjectPrivate::memberIndexes(receiver, method, &dummy, &method_index);
}
const QMetaObject *smeta = sender->metaObject();
const QMetaObject *rmeta = receiver->metaObject();
if (signal_index == -1) {
qWarning("QObject::connect: Can't find signal %s on instance of class %s",
signal.methodSignature().constData(), smeta->className());
return QMetaObject::Connection(0);
}
if (method_index == -1) {
qWarning("QObject::connect: Can't find method %s on instance of class %s",
method.methodSignature().constData(), rmeta->className());
return QMetaObject::Connection(0);
}
if (!QMetaObject::checkConnectArgs(signal.methodSignature().constData(), method.methodSignature().constData())) {
qWarning("QObject::connect: Incompatible sender/receiver arguments"
"\n %s::%s --> %s::%s",
smeta->className(), signal.methodSignature().constData(),
rmeta->className(), method.methodSignature().constData());
return QMetaObject::Connection(0);
}
检查当作参数的信号和槽函数是否真实存在,如果不存在也返回空。
if (!QMetaObject::checkConnectArgs(signal.methodSignature().constData(), method.methodSignature().constData())) {
qWarning("QObject::connect: Incompatible sender/receiver arguments"
"\n %s::%s --> %s::%s",
smeta->className(), signal.methodSignature().constData(),
rmeta->className(), method.methodSignature().constData());
return QMetaObject::Connection(0);
}
检查信号的形参是否一致。
int *types = 0;
if ((type == Qt::QueuedConnection)
&& !(types = queuedConnectionTypes(signal.parameterTypes())))
return QMetaObject::Connection(0);
如果采用多线程的枚举来连接,要检查参数是否为元数据,如果是自定义的数据,那么必须调用qRegisterMetaType进行注册,不然会连接失败。另外,如果参数是引用类型,也无法跨线程连接。
#ifndef QT_NO_DEBUG
check_and_warn_compat(smeta, signal, rmeta, method);
#endif
QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
sender, signal_index, signal.enclosingMetaObject(), receiver, method_index, 0, type, types));
return handle;
连接成功。另外,对于重载的情况,我们可以使用QOverload<T>::of来进行转化,达到信号和曹的匹配。
4,bool blockSignals(bool b) Q_DECL_NOTHROW;
源码很简单,没啥好说的,作用是,当b为真的时候,阻塞信号的发出,为假的时候,取消阻塞。
5,int startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer);
这个接口用来启动一个定时器事件,并返回定时器id,如果失败就返回0。定时器每隔interval 毫秒就会启动一次,直到调用killTimer()。当定时器发生时,会调用timerEvent(QTimerEvent *event).如果多个定时器在运行,可用通过定时器id来区分。
if (Q_UNLIKELY(interval < 0)) {
qWarning("QObject::startTimer: Timers cannot have negative intervals");
return 0;
}
时间不能为负数。
if (Q_UNLIKELY(!d->threadData->eventDispatcher.load())) {
qWarning("QObject::startTimer: Timers can only be used with threads started with QThread");
return 0;
}
这也是经常能碰到的情况。可以通过连接 QThread :: started信号 触发的槽函数启动定时器来解决。
if (Q_UNLIKELY(thread() != QThread::currentThread())) {
qWarning("QObject::startTimer: Timers cannot be started from another thread");
return 0;
}
定时器所处的线程要和当前调用的线程相同才行
int timerId = d->threadData->eventDispatcher.load()->registerTimer(interval, timerType, this);
if (!d->extraData)
d->extraData = new QObjectPrivate::ExtraData;
d->extraData->runningTimers.append(timerId);
return timerId;
接下来就启动成功,返回相应的定时器id。
6, void killTimer(int id);
用法就是停止对应的定时器。
Q_D(QObject);
if (Q_UNLIKELY(thread() != QThread::currentThread())) {
qWarning("QObject::killTimer: Timers cannot be stopped from another thread");
return;
}
也是经常能遇到的问题,我们定时器对象所处的线程必须和当前调用函数的线程相同。
if (id) {
int at = d->extraData ? d->extraData->runningTimers.indexOf(id) : -1;
if (at == -1) {
// timer isn't owned by this object
qWarning("QObject::killTimer(): Error: timer id %d is not valid for object %p (%s, %s), timer has not been killed",
id,
this,
metaObject()->className(),
qPrintable(objectName()));
return;
}
这个定时器id必须由这个对象产生的才能调用。
if (d->threadData->eventDispatcher.load())
d->threadData->eventDispatcher.load()->unregisterTimer(id);
d->extraData->runningTimers.remove(at);
QAbstractEventDispatcherPrivate::releaseTimerId(id);
开始定时器就是d->extraData->runningTimers加入时间id,结束就是remove掉。
(这个图太久了,忘记在哪里看到了,直接用了,如果侵犯了您的权益请私信本人删除)