深刻了解Qt(一)之QObject

深刻了解Qt主要内容来源于Inside Qt系列,本文作了部分删改,以便于理解。在此向原做者表示感谢!html

QObject这个 class 是 QT 对象模型的核心,关于对象模型能够阅读C++对象模型详解,绝大部分的 QT 类都是从这个类继承而来。这个模型的中心特征就是一个叫作信号和槽(signaland slot)的机制来实现对象间的通信,你能够把一个信号和另外一个槽经过 connect(…) 方法链接起来,并可使用disconnect(…) 方法来断开这种链接,你还能够经过调用blockSignal(…) 这个方法来临时的阻塞信号。对于信号和槽能够阅读Qt 信号和槽函数安全

QObject 的对象树机制:ide

当你建立一个 QObject 并使用其它对象做为父对象时,这个对象会自动添加到父对象的children() list 中。父对象拥有这个对象,好比,它将在它的析构函数中自动删除它全部的 child对象。你能够经过 findChild() 或者findChildren()函数来查找一个对象。每一个对象都有一个对象名称(objectName())和类名称(class name), 他们均可以经过相应的 metaObject 对象来得到。你还能够经过 inherits() 方法来判断一个对象的类是否是从另外一个类继承而来。当对象被删除时,它发出destroyed()信号。你能够捕获这个信号来避免对QObject的无效引用。QObject能够经过event()接收事件而且过滤其它对象的事件。详细状况请参考installEventFilter()和eventFilter()。对于每个实现了信号、槽和属性的对象来讲,Q_OBJECT 宏都是必需要加上的。函数

QObject 类的实现文件一共有四个:
* qobject.h,QObject class 的基本定义,也是咱们通常定义一个类的头文件。
* qobject.cpp,QObject class 的实现代码基本上都在这个文件。
* qobjectdefs.h,这个文件中最重要的东西就是定义了 QMetaObject class,这个class是为了实现 signal、slot、properties,的核心部分。
* qobject_p.h,这个文件中的 code 是辅助实现QObject class 的,这里面最重要的东西是定义了一个 QObjectPrivate 类来存储 QOjbect 对象的成员数据。post

理解这个 QObjectPrivate class 又是咱们理解 QT kernel source code 的基础,这个对象包含了每个 QT 对象中的数据成员,好了,让咱们首先从理解 QObject 的数据存储代码开始我么的 QT Kernel Source Code 之旅。学习

咱们知道,在C++中,几乎每个类(class)中都须要有一些类的成员变量(class member variable),在一般状况下的作法以下:url

class Person
{
private:
string mszName; // 姓名
bool mbSex;    // 性别
int mnAge;     // 年龄
};

在QT中,却几乎都不是这样作的,那么,QT是怎么作的呢?spa

几乎每个C++的类中都会保存许多的数据,要想读懂别人写的C++代码,就必定须要知道每个类的的数据是如何存储的,是什么含义,不然,咱们不可能读懂别人的C++代码。在这里也就是说,要想读懂QT的代码,第一步就必须先搞清楚QT的类成员数据是如何保存的。.net

为了更容易理解QT是如何定义类成员变量的,咱们先说一下QT 2.x 版本中的类成员变量定义方法,由于在 2.x 中的方法很是容易理解。而后在介绍 QT 4.4 中的类成员变量定义方法。指针

QT 2.x 中的方法

在定义class的时候(在.h文件中),只包含有一个类成员变量,只是定义一个成员数据指针,而后由这个指针指向一个数据成员对象,这个数据成员对象包含全部这个class的成员数据,而后在class的实现文件(.cpp文件)中,定义这个私有数据成员对象。示例代码以下:

// File name:  person.h
 struct PersonalDataPrivate; // 声明私有数据成员类型
 class Person
{
public:
 Person ();   // constructor
virtual ~Person ();  // destructor
void setAge(const int);
int getAge();
 private:
 PersonalDataPrivate* d;
};
 //---------------------------------------------------------------------
// File name:  person.cpp
 struct PersonalDataPrivate  // 定义私有数据成员类型
{
string mszName; // 姓名
bool mbSex;    // 性别
int mnAge;     // 年龄
};
 
// constructor
Person::Person ()
{
d = new PersonalDataPrivate;
};
 
// destructor
Person::~Person ()
{
delete d;
};
 
void Person::setAge(const int age)
{
if (age != d->mnAge)
d->mnAge = age;
}
 
int Person::getAge()
{
return d->mnAge;
}

在最初学习QT的时候,我也以为这种方法很麻烦,可是随着使用的增多,我开始很喜欢这个方法了,并且,如今我写的代码,基本上都会用这种方法。具体说来,它有以下优势:

减小头文件的依赖性
把具体的数据成员都放到cpp文件中去,这样,在须要修改数据成员的时候,只须要改cpp文件而不须要头文件,这样就能够避免一次由于头文件的修改而致使全部包含了这个文件的文件所有从新编译一次,尤为是当这个头文件是很是底层的头文件和项目很是庞大的时候,优点明显。
同时,也减小了这个头文件对其它头文件的依赖性。能够把只在数据成员中须要用到的在cpp文件中include一次就能够,在头文件中就能够尽量的减小include语句
加强类的封装性
这种方法加强了类的封装性,没法再直接存取类成员变量,而必须写相应的 get/set 成员函数来作这些事情。
关于这个问题,仁者见仁,智者见智,每一个人都有不一样的观点。有些人就是喜欢把类成员变量都定义成public的,在使用的时候方便。只是我我的不喜欢这种方法,当项目变得很大的时候,有很是多的人一块儿在作这个项目的时候,本身所写的代码处于底层有很是多的人须要使用(#include)的时候,这个方法的弊端就充分的体现出来了。

还有,我不喜欢 QT 2.x 中把数据成员的变量名都定义成只有一个字母d,看起来很不直观,尤为是在search的时候,很不方便。可是,QT kernel 中的确就是这么干的。

QT 4.4.x 中的方法

在 QT 4.4 中,类成员变量定义方法的出发点没有变化,只是在具体的实现手段上发生了很是大的变化,在 QT 4.4 中,使用了很是多的宏来作事,这凭空的增长了理解 QT source code 的难度,不知道他们是否是从MFC学来的。就连在定义类成员数据变量这件事情上,也大量的使用了宏。

在这个版本中,类成员变量再也不是给每个class都定义一个私有的成员,而是把这一项common的工做放到了最基础的基类 QObject 中,而后定义了一些相关的方法来存取,好了,让咱们进入具体的代码吧。

// file name: qobject.h
 
class QObjectData
{
public:
virtual ~QObjectData() = 0;
// 省略
};
 
class QObject
{
Q_DECLARE_PRIVATE(QObject)
 
public:
 
QObject(QObject *parent=0);
 
protected:
 
QObject(QObjectPrivate &dd, QObject *parent = 0);
QObjectData *d_ptr;
}

这些代码就是在 qobject.h 这个头文件中的。在 QObject class 的定义中,咱们看到,数据员的定义为:QObjectData*d_ptr; 定义成 protected 类型的就是要让全部的派生类均可以存取这个变量,而在外部却不能够直接存取这个变量。而 QObjectData 的定义却放在了这个头文件中,其目的就是为了要全部从QObject继承出来的类的成员变量也都相应的要在QObjectData这个class继承出 来。而纯虚的析构函数又决定了两件事:

* 这个class不能直接被实例化。换句话说就是,若是你写了这么一行代码,new QObjectData, 这行代码必定会出错,compile的时候是没法过关的。
* 当 delete 这个指针变量的时候,这个指针变量是指向的任意从QObjectData继承出来的对象的时候,这个对象都能被正确delete,而不会产生错误,诸如,内存泄漏之类的。

咱们再来看看这个宏作了什么,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;

这个宏主要是定义了两个重载的函数,d_func(),做用就是把在QObject这个class中定义的数据成员变量d_ptr安全的转换成为每个具 体的class的数据成员类型指针。咱们看一下在QObject这个class中,这个宏展开以后的状况,就一幕了然了。

Q_DECLARE_PRIVATE(QObject)展开后,就是下面的代码:

inline QObjectPrivate* d_func() { return reinterpret_cast<QObjectPrivate *>(d_ptr); }
inline const QObjectPrivate* d_func() const
{ return reinterpret_cast<const QObjectPrivate *>;(d_ptr); } \
friend class QObjectPrivate;

宏展开以后,新的问题又来了,这个QObjectPrivate是从哪里来的?在QObject这个class中,为何不直接使用QObjectData来数据成员变量的类型?

还记得咱们刚才说过吗,QObjectData这个class的析构函数的纯虚函数,这就说明这个class是不能实例化的,因此,QObject这个class的成员变量的实际类型,这是从QObjectData继承出来的,它就是QObjectPrivate !

这个 class 中保存了许多很是重要并且有趣的东西,其中包括 QT 最核心的 signal 和slot 的数据,属性数据,等等,咱们将会在后面详细讲解,如今咱们来看一下它的定义:

下面就是这个class的定义:

class QObjectPrivate : public QObjectData
{
Q_DECLARE_PUBLIC(QObject)
 
public:
 
QObjectPrivate(int version = QObjectPrivateVersion);
virtual ~QObjectPrivate();
// 省略
}

那么,这个 QObjectPrivate 和 QObject 是什么关系呢?他们是如何关联在一块儿的呢?

接上节,让咱们来看看这个 QObjectPrivate 和 QObject 是如何关联在一块儿的。

// file name: qobject.cpp
 
QObject::QObject(QObject *parent)
: d_ptr(new QObjectPrivate)
{
// ………………………
}
 
QObject::QObject(QObjectPrivate &dd, QObject *parent)
: d_ptr(&dd)
{
// …………………
}

从第一个构造函数能够很清楚的看出来,QObject class 中的 d_ptr 指针将指向一个 QObjectPrivate 的对象,而QObjectPrivate这个class是从QObjectData继承出来的。

这第二个构造函数干什么用的呢?从 QObject class 的定义中,咱们能够看到,这第二个构造函数是被定义为protected 类型的,这说明,这个构造函数只能被继承的class使用,而不能使用这个构造函数来直接构造一个QObject对象,也就是说,若是写一条下面的语句, 编译的时候是会失败的,

[cpp]  view plain  copy
  1. new QObject(*new QObjectPrivate, NULL);  

为了看的更清楚,咱们以QWidget这个class为例说明。

QWidget是QT中全部UI控件的基类,它直接从QObject继承而来, 

[cpp]  view plain  copy
  1. class QWidget : public QObject, public QPaintDevice  
  2. {  
  3. Q_OBJECT  
  4. Q_DECLARE_PRIVATE(QWidget)  
  5. // .....................  
  6. }  

咱们看一个这个class的构造函数的代码:

[cpp]  view plain  copy
  1. QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)  
  2. : QObject(*new QWidgetPrivate, 0), QPaintDevice()  
  3. {  
  4. d_func()->init(parent, f);  
  5. }  

很是清楚,它调用了基类QObject的保护类型的构造函数,而且以 *new QWidgetPrivate 做为第一个参数传递进去。也就是说,基类(QObject)中的d_ptr指针将会指向一个QWidgetPrivate类型的对象。

再看QWidgetPrivate这个class的定义:

[cpp]  view plain  copy
  1. class QWidgetPrivate : public QObjectPrivate  
  2. {  
  3. Q_DECLARE_PUBLIC(QWidget)  
  4. // .....................  
  5. };  

好了,这就把全部的事情都串联起来了。

关于QWidget构造函数中的惟一的语句 d_func()->init(parent, f) 咱们注意到在class的定义中有这么一句话: Q_DECLARE_PRIVATE(QWidget)

咱们前面讲过这个宏,当把这个宏展开以后,就是这样的:

[cpp]  view plain  copy
  1. inline QWidgetPrivate* d_func() { return reinterpret_cast<QWidgetPrivate *>(d_ptr); }  
  2. inline const QWidgetPrivate* d_func() const  
  3. return reinterpret_cast<const QWidgetPrivate *>(d_ptr); } \  
  4. friend class QWidgetPrivate;  

很清楚,它就是把QObject中定义的d_ptr指针转换为QWidgetPrivate类型的指针。

小结:

要理解QT Kernel的code,就必需要知道QT中每个Object内部的数据是如何保存的,而QT没有象咱们平时写code同样,把全部的变量直接定义在类中,因此,不搞清楚这个问题,咱们就没法理解一个相应的class。其实,在QT4.4中的类成员数据的保存方法在本质是与QT2.x中的是同样的,就是在class中定义一个成员数据的指针,指向成员数据集合对象(这里是一个QObjectData或者是其派生类)。初始化这个成员变量的办法是定义一个 保护类型的构造函数,而后在派生类的构造函数new一个派生类的数据成员,并将这个新对象赋值给QObject的数据指针。在使用的时候,经过预先定义个宏里面的一个inline函数来把数据指针在安全类型转换,就可使用了。

相关文章
相关标签/搜索