cnn是什么意思QT源码分析:QObject
QT框架⾥⾯最⼤的特⾊就是在C++的基础上增加了元对象系统(Meta-Object System),⽽元对象系统⾥⾯最重要的内容就是信号与槽机制,这个机制是在C++语法的基础上实现的,使⽤了函数、函数指针、回调函数等概念。当然与我们⾃⼰去写函数所不同的是槽与信号机制会⾃动帮我们⽣成部分代码,⽐如我们写的信号函数就不需要写它的实现部分,这是因为在我们编译程序的时候,编译器会⾃动⽣成这⼀部分代码,当我们调⽤connect函数的时候,系统会⾃动将信号函数与槽函数相连接,于是当我们调⽤信号函数的时候,系统就会⾃动回调槽函数,不管你是在同⼀线程下调⽤或者在不同线程下调⽤,系统都会⾃动评估,并在合理的时候触发函数,以此来保证线程的安全。信号与槽机制是线程安全的,这可以使得我们在调⽤的时候不⽤再额外的增加过多保证线程同步的代码,为了实现元对象系统,QT把所有相关实现写在了QObject 类中,所以当你想使⽤元对象系统的时候,你所写的类需要继承⾃QObject,包括QT⾃带的所有类都是继承⾃QObject,所以分析QObject的代码,对了解QT的元对象机制有⾮常⼤的帮助,我并不打算把QObject类的每⼀⾏代码都写下来,只想把其中⽐较关键的内容或者对分析QT源码有帮助的内容介绍⼀下。
1.宏Q_OBJECT
这个宏展开以后是如下定义:
#define Q_OBJECT
public:
QT_WARNING_PUSH
Q_OBJECT_NO_OVERRIDE_WARNING
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
private:
Q_OBJECT_NO_ATTRIBUTES_WARNING
pearl
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call,int,void**);
QT_WARNING_POP
struct QPrivateSignal {};
QT_ANNOTATE_CLASS(qt_qobject,"")
你可以看到这个宏定义了⼀些函数,并且函数名都带有meta,所以不难猜到这些函数和QT的元对象系统是有关系的,实际上你在qobject.cpp⾥⾯是找不到这些函数的实现的,它们的实现都在moc_qobject.cpp⾥⾯。QT的元对象系统是这样处理的,当你编译你的⼯程时,它会去遍历所有C++⽂件,当发现某⼀个类的私有部分有声明Q_OBJECT这个宏时,就会⾃动⽣成⼀个moc_*.cpp的⽂件,这个⽂件会⽣成信号的实现函数,Q_OBJECT宏⾥⾯定义的那些函数也会在这个⽂件⾥⾯实现,并⽣成与类相关的元对象。这就是为什么我们定义⼀个信号的时候,不需要实现它,因为它的实现已经写在moc_*.cpp⽂件⾥⾯了。
alphabetically
2.Q_PROPERTY(QString objectName READ objectName WRITE tObjectName NOTIFY objectNameChanged)
这个宏是定义⼀个属性,属性也是元对象系统的内容之⼀,实际上我们在做界⾯设计的时候经常会⽤到属性,⽐如修改Label的显⽰内容,需要⽤到Text属性,修改窗体长宽等等,在你做界⾯设计的时候,属性编辑框⾥⾯所显⽰的就是当前对象的所有属性,⽽这些属性的定义就是⽤上⾯的宏来定义的。实际上属性和变量是有点相似的,都是读值和写值的功能,那为什么不直接对变量操作就好了?虽然看起来相似,但是还是有不同点:
第⼀: 属性可以定义为可读写的,也可以定义为只读的,⽐如有些数据我们只在类的内部做修改不允许在外部做修改,但是有时候⼜需要在外部查看这个值,就可以设置为只读属性,⽽变量是做不到这点的,你把变量放在public部分,那么这个变量就可以在任何地⽅被修改,这就破坏了类的封装性。
第⼆: 属性可以定义信号,当属性变化的时候触发信号,这样我们可以在信号触发时做⼀些⼯作,⽐如当你设置LineEdit为readonly 时,你会发现输⼊框的背景颜⾊被改变了,这就可以通过属性变化的信号来处理。
3.Q_DECLARE_PRIVATE(QObject)
这个宏的定义如下:
#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;
这个宏⾸先创建了两个内联函数,返回值都是QObjectPrivate ,并且声明QObjectPrivate 为友元类,QObjectPrivate这个类是在qobject_p.h中定义,它继承⾄QObjectData,你可以看到d_func()是将d_prt强制转换为QObjectPrivate*类型,⽽d_prt这个指针在QObject⾥⾯定义的是QObjectData的指针类型,所以这⾥可以进⾏强转,QObjectPrivate这个类主要存放QOject类需要⽤到的⼀些⼦对象,变量等。为什么要介绍这个宏,如果你有看QT源码习惯的话,你会发现⼏乎每⼀个类都⽤到了这个宏,我们⾃⼰写的类会经常把类内部⽤的变量声明在private部分,但是QT源码并不是这样做的,它的做法是给每个类创建⼀个以类名+Private的类,例如QObject对应的就是QObjectPrivate,这个类实际上就是⽤来存放QObject需要⽤到的所有私有变量和私有对象,⽽QObject更多的是函数实现,你去看其他的源码也是如此,⼦对象声明在QPrivate中,⽽本类只实现函数。
4.构造函数
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;
tParent(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);
}
(1)⾸先第⼀步就创建d_ptr指针。
(2)Q_D(QObject);这个宏你可以在QT的很多源码⾥⾯看到。它展开以后是下⾯的样⼦:
#define Q_D(Class) Class##Private * const d = d_func();
d_fun()函数前⾯讲到了,其实就是返回d_ptr了。所以这个宏的意思是定义⼀个指针d指向d_ptr;
(3)d_ptr->q_ptr = this;
q_ptr是QOject类型,这⾥把this指针赋给了它,所以使得QObjectPrivate可以回调QOject的函数。(4)初始化threadData
(1)如果要移动的线程和Object本⾝就是同⼀线程,那么直接返回
if(d->threadData->thread == targetThread){高中家教
// object is already in this thread
return;
}
夏虫岂可语冰
(2)如果parent不为空,不允许移动到其他线程,⼦类必需与⽗类在同⼀线程。
china unicom
if(d->parent !=0){
qWarning("QObject::moveToThread: Cannot move objects with a parent");
return;
}
(3)如果对象是窗体类,不允许移动到线程,窗体类必需在主线程运⾏,在⼦线程去直接调⽤窗体控件都是不安全的,可能导致程序崩溃,合理的做法是通过信号槽机制。
if(d->isWidget){
qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread");
return;
}
(4)只有在对象所在线程才能将对象移动到另⼀个线程,不能在其他线程将对象移动到某个线程,这种操作是不被允许的。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;
}el 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);
#ifdef Q_OS_MAC
qWarning("You might be loading two ts of Qt binaries into the same process. "
"Check that all plugins are compiled against the right Qt binaries. Export "
"DYLD_PRINT_LIBRARIES=1 and check that only one t of binaries are being loaded.");
#endif温莎城堡
return;
}
//为转移线程准备,遍历所有⼦对象,并给每⼀个⼦对象发送⼀个QEvent::ThreadChange的事件。
d->moveToThread_helper();
if(!targetData)
targetData =new QThreadData(0);
//为转移事件上锁
QOrderedMutexLocker locker(¤tData->postEventList.mutex,oddnumber
&targetData->postEventList.mutex);
currentData->ref();
//遍历所有⼦对象及⾃⾝,将currentData的postEventList⾥⾯的对象转移到targetData,将所有⼦对象及⾃⾝的threadData设置为targetData
d_func()->tThreadData_helper(currentData, targetData);
whisperlocker.unlock();
currentData->deref();
connet的重构函数很多,这⾥选择其中⼀个来分析。
QMetaObject::Connection QObject::connect(const QObject *nder,const QMetaMethod &signal,
const QObject *receiver,const QMetaMethod &method,
Qt::ConnectionType type)
(1)⾸选nder,receiver不能为空,signal必须是Signal类型,也就是声明在signals:下⾯,method不能为构造函数,不满⾜这⼏个条件则返回。
if(nder ==0
|| receiver ==0
|| hodType()!= QMetaMethod::Signal
|| hodType()== QMetaMethod::Constructor){
qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
nder ? nder->metaObject()->className():"(null)",
receiver ? receiver->metaObject()->className():"(null)",
return QMetaObject::Connection(0);
}
(2)检查signal和method是否真实存在,在编译期即使传⼊的信号不存在也不会报错,在运⾏期会检查是否存在,所以在写connect函数的时候要仔细检查,尽量使⽤&ClassName::functionName的⽅式让系统⾃动补全,当然也可以通过connect的返回值来判断调⽤是否成功,如调⽤不成功则抛出异常。