This is a repost from my previous Qt learning series, based on Qt 4.3.
之前的笔记: QT4学习笔记 (1). 可以复习一下, 修改了一些错误.
今天主要说一下Qt的实现, 主要是内存方面. 书上没有写, 完全是自己看源码的, 版本4.3.3 commercial.
先来还是那段最简单的Qt代码:
1 2 3 4 5 6 7 |
int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel *label = new QLabel("Hello Qt!"); label->show(); return app.exec(); } |
吐了没?= =b.. 还是那个new出来的QLabel
的问题, Qt到底怎么样来回收的呢? debug的时候, 我直接把断点设在了QObject
和QWidget
类的析构函数(直接用debug模式, 打开源码设断点运行就行了). 事实证明还不够. 通过看call stack, 最终发现析构的过程从QApplication
这个类开始的.
/src/gui/kernel/qapplication.cpp
, 析构函数中, 有这么一段:
1 2 3 4 5 6 7 8 9 10 11 |
// delete widget mapper if (QWidgetPrivate::mapper) { QWidgetMapper * myMapper = QWidgetPrivate::mapper; QWidgetPrivate::mapper = 0; for (QWidgetMapper::Iterator it = myMapper->begin(); it != myMapper->end(); ++it) { register QWidget *w = *it; if (!w->parent()) // window w->destroy(true, true); } delete myMapper; } |
可能有点难懂, 反正大写Q开头的都是类名. 这段意思大概就是QApplication
类会维护一个所有运行中QWidget
的mapper, 以便析构函数中作相应处理. 这里似乎只看到调用QWidget
的destory()
函数, 没有释放内存. debug的结果也验证了, QLabel
的析构函数没有被调用到. 难道Qt不具备防止memory leak的么?
我们改一下代码, 把heap内存改成stack内存:
1 2 3 4 5 6 7 |
int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label("Hello Qt!"); label.show(); return app.exec(); } |
这个显然是没有问题的. 那我们应该怎么用Qt, 才能利用Qt自带的memory管理功能, 只管new, 不用自己delete呢? 再看一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
int main(int argc, char *argv[]) { QApplication app(argc, argv); { QLabel *label = new QLabel("Hello!"); QHBoxLayout *layout = new QHBoxLayout; layout->addWidget(label); QDialog dialog; dialog.setLayout(layout); dialog.show(); } return app.exec(); } |
(以上sb代码debug之用, 切勿模仿…)
我们在QDialog
上加了一个QLabel
, QDialog
是在stack上创建的. debug中, 当QDialog
对象调用析构函数时, 在/src/gui/kernel/qwidget.cpp
中, 会调用QWidget
的析构函数. 在QWidget
的析构函数中, 有这样一段:
1 2 |
if (!d->children.isEmpty()) d->deleteChildren(); |
d是什么东西? 继续跟进, 发现d是一个QObjectPrivate
类的对象, 其中的确有delete的操作(废话, 老子依据call stack反推的啊). 于是我们清楚了, Qt的类之间有父子关系(这里指的是包含, 而不是继承关系), 一旦父类对象被被delete的话, 子类也会被相应的delete. 所以, Qt中我们一般都不需要调用delete的. 但是有一个前提: 由于deleteChildren()
函数是QObjectPrivate
类的函数, 要使以上条件成立的话, 所有的包含关系的类必须都继承自QObject
. 这里有一个design pattern, composite, 嗯.
另外有一点, QObject
类没有定义拷贝构造函数, 也就是说一般的赋值都是shadow copy. 其实是有道理的, 当我们copy一个QObject
的时候, 新的object到底是继承原来的children还是不继承呢? 呵呵.
最后我们再来花点时间讨论一下QObject
和QObjectPrivate
类的关系. 在/src/corelib/global/qglobal.h
中, 有以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 |
#define Q_D(Class) Class##Private * const d = d_func() #define Q_Q(Class) Class * const q = q_func() #define Q_DECLARE_PRIVATE(Class) \ inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(d_ptr); } \ inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(d_ptr); } \ friend class Class##Private; #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; |
额.. 也就是说, 一般QXxx类和QXxxPrivate类互为friend类, 一般来说QXxxPrivate类都是一些底层实现, 不属于对外的Qt API. 而QXxx类则是对外的Qt API. 有比如说有QProcess
和QProcessPrivate
两个类, 在/src/corelib/io
下, 对应4个文件, qprocess.h
, qprocess.cpp
, qprocess_p.h
, qprocess_win.cpp
. 前2个是对外的Qt API, *_p.h
是QProcessPrivate
类头文件, *_win.cpp
则是对应的平台相关的实现代码.
好了, 下班了, 明天再写…