为什么需要对象模型?
GUI 编程中通常需要管理相当多的对象,Qt 提供的对象模型结合了标准 C++ 的运行效率及它所没有的高灵活性。说白了,是因为标准 C++ 没有提供 Qt GUI 编程所需的对象模型,Qt 才设计出自己的对象模型(Qt Object Model)的。
Qt 向 C++ 扩展出以下功能:
- 一个强大地无缝对象通信机制:信号与槽
- 可查询和可设计的对象属性
- 强大的事件和事件过滤器
- 用于国际化的多语言翻译机制
- 一个内部计时器
- 一个受保护的指针(QPoint),当它指向的对象被析构后自动置为 0
以上这些功能是基于 QObject 的,也就是说基于 Qt 基类的。这样设计出的 Qt 对象模型才能实现上面的这些功能。
把 Qt 对象当作标识( Identity,ID)而非值 (Value)
Qt 扩展出 C++ 的一些特性,要求 Qt 对象应该被看作是标识而非值。标识和值有着很大的不一样:值是用来被复制或者赋值的;而标识是被克隆(cloned)的,这意味着创建 Qt 对象不需要精确地复制旧对象。打个比方,有一对双保胎,她们长得一模一样,但是她们有不同的名字、此刻处在不同的的位置、甚至完全不同的社交圈。
标识和值的关系是否可以类似于函数传参中的“传引用”与“传值”?
一个 Qt 对象有以下需求:
- 可能唯一的 QObject::objectName()。复制它时如何怎样给新对象取名?
- 在对象树中有特定的位置。如果要复制它,新对象应该放在对象树的哪里?
- 一个对象可能通过信号/槽连接到另一个对象。复制的时候怎么把这种连接复制过去?
- 可以在运行时动态添加属性。复制的时候,怎样把旧对象的动态属性包括进去?
以上种种要求 Qt 对象必须是标识而非值。因为是值的话,无法处理上述几点需求。因此,QObject 的派生类都要求禁用拷贝构造函数和赋值操作符。
class MyClass : public QObject
{
private:
Q_DISABLE_COPY(MyClass) // 禁用拷贝构造函数和赋值操作符
};
Qt 中的对象树是怎样的一棵树?
- Qt 对象存在于一颗对象树中。当创建一个对象并指定了它的父对象时,就把它加入了父对象所在的对象树中。这个对象就是它父对象的儿子,当父对象析构时它也被析构。这种构造对象树的方法在 GUI 对象中被证明是非常好用的。例如 QWindow(窗口) 对象有一个子对象 QShortcut(快捷键),当窗口关闭时,快捷键这个子对象也会随着删除。
- QQuickItem 分虚拟父对象和数据父对象,前者用于布局,后者用于内存管理。
- QWidget 是所有 Qt Widgets 的基类,它扩展出它们的
父-子
关系。一个子对象通常也会成为子部件,也就是说它显示在父对象的坐标系统中,并且受限于父对象的坐标边界(将坐标移到父对象以外的部分无法显示)。例如,对话框关掉时,它上面的按钮也被析构。 - 可以用
QObject::dumpObjectTree()
和QObject::dumpObjectInfo()
来调试对象树。
对象的构造和析构顺序
分两种情况:在堆中创建和在栈中创建。
- 在堆中创建(用 new 创建)的对象,假如它指定了父对象,那么对象树会自动管理它的内存,Qt 保证不会析构两次。
- 在栈中创建的对象,由于标准 C++ 的规定局部对象的析构顺序与构造顺序是相反的,所以要求父子对象有正确的创建顺序。
int main()
{
QWidget window;
QPushButton quit("Quit", &window);
...
}
// 析构时先析构 quit,quit 从父对象 window 上移除,再析造 window,不会有任何问题。
int main()
{
QPushButton quit("Quit");
QWidget window;
quit.setParent(&window);
...
}
// 析构时先析构 window,它的子对象 quit 也随之被析构;按照 C++ 的析造顺序,quit 将再次析构一次,造成程序崩溃!