Qt | 一文总结QObject
Qt | 一文总结QObject
来源 https://zhuanlan.zhihu.com/p/608004472
一、QObject的重要知识
QObject
是Qt对象模型的核心。这个模型的核心特性是一个强大的无缝对象通信机制,即信号和槽。可以使用connect()
将信号连接到槽函数,并使用disconnect()
破坏已经存在的连接。为了避免永不结束的通知循环,可以使用blockSignals()
暂时阻塞信号。受保护的函数connectNotify()
和disconnectNotify()
可以用于跟踪信号连接。
Qt中,以QObject
为基础形成了一棵“对象树”,当使用另一个对象作为父对象创建QObject
时,该对象将自动将自己添加到父对象的children()
列表中。此后父对象拥有该对象的所有权,则会自动删除析构函数中的子元素。在开发中可以使用findChild()
或findChildren()
根据名称和可选的类型查找子对象。
每个对象都有一个objectName()
,可以通过相应的metaObject()
找到它的类名(函数:QMetaObject::className()
)。在实际开发中可以使用inherits()
函数确定对象的类是否继承了QObject
继承层次结构中的另一个类。当一个对象被删除时,会发出destroyed()
信号,通过这一点可以捕获此信号,避免对QObject
进行悬挂引用。
二、QObject重要成员函数
本小节总结在开发中,QObject中常使用的成员函数和重要宏定义。
1、事件获取和处理API
/* 在此对象上安装事件筛选器filterObj */
void QObject::installEventFilter(QObject *filterObj)
/* 这个虚拟函数接收对象的事件,如果事件e被识别并处理,则返回true */
bool QObject::event(QEvent *e)
2、对象的线程关联API
/* 返回对象所在的线程。 */
QThread *QObject::thread() const
/* 更改对象及其子对象的线程关联性。如果一个对象有父对象,则不能移动该对象到另一个线程中 */
void QObject::moveToThread(QThread *targetThread)
3、获取子对象API
/* 返回该对象具有给定名称的所有可转换为类型T的子对象,如果没有此类对象,则返回一个空列表 */
QList<T> findChildren(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
/* 返回子对象列表 */
const QObjectList & children() const
4、qobject_cast
函数原型如下:
T qobject_cast(QObject* object)
T qobject_cast(const QObject* object)
如果对象类型为T(或子类),则将给定的对象转换为类型T;否则返回nullptr。如果对象是nullptr,那么它也将返回nullptr。
注意:类T必须继承(直接或间接)QObject
并使用Q_OBJECT
宏声明。
qobject_cast()
函数的行为类似于标准c++ dynamic_cast()
,它的优点是不需要RTTI
(Run-Time Type Identification-运行时类型识别)支持,并且可以跨动态库边界工作。
5、事件处理相关函数
//此虚函数用于接收对象的事件,如果事件e被识别和处理,则返回true。
virtual bool event(QEvent *e)
//如果此对象已作为被监视对象的事件过滤器安装,则过滤事件。
virtual bool eventFilter(QObject *watched, QEvent *event)
//从该对象中移除事件筛选器对象obj。
void removeEventFilter(QObject *obj)
//在对象上安装事件筛选器filterObj
void installEventFilter(QObject *filterObj)
6、定时器相关函数
//启动计时器并返回计时器标识符
int startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer)
//启动计时器并返回计时器标识符
int startTimer(std::chrono::milliseconds time, Qt::TimerType timerType = Qt::CoarseTimer)
//使用定时器标识符id终止计时器
void killTimer(int id)
7、重要宏定义
- Q_DISABLE_COPY(Class)
禁止对给定类使用复制构造函数和赋值运算符。 - Q_DISABLE_COPY_MOVE(Class)
该宏用于禁用给定类的复制构造函数、赋值运算符、移动构造函数和移动赋值运算符,将Q_DISABLE_COPY
和Q_DISABLE_MOVE
组合在一起。
- Q_DISABLE_MOVE(Class 5.13)
禁止对给定类使用移动构造函数和移动赋值操作符。
- Q_EMIT
当希望使用第三方信号/槽函数机制来使用Qt信号和槽函数时,可以使用此宏替代emit
关键字来发出信号。
- Q_ENUM(...)
这个宏用于向元对象系统注册一个枚举类型。该宏必须放在enum
声明之后,且放在具有Q_OBJECT
或Q_GADGET
宏的类中。对于命名空间,应该使用Q_ENUM_NS()
。例如:
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass(QObject *parent = nullptr);
~MyClass();
enum Priority { High, Low, VeryHigh, VeryLow };
Q_ENUM(Priority)
void setPriority(Priority priority);
Priority priority() const;
};
- Q_ENUM_NS(...)
这个宏向元对象系统注册一个枚举类型。它必须放在enum
声明之后,,且具有Q_NAMESPACE
宏的名称空间中。与Q_ENUM
相同,但在命名空间中。
- Q_FLAG(...)
这个宏向元对象系统中注册一个单标记类型。它通常用于类定义中,以声明给定enum
的值可以用作标志,并使用按位或运算符进行组合。对于命名空间,应该使用Q_FLAG_NS()
。例如:
class QLibrary : public QObject
{
Q_OBJECT
public:
...
enum LoadHint {
ResolveAllSymbolsHint = 0x01,
ExportExternalSymbolsHint = 0x02,
LoadArchiveMemberHint = 0x04
};
Q_DECLARE_FLAGS(LoadHints, LoadHint)
Q_FLAG(LoadHint)
...
}
- Q_INTERFACES(...)
这个宏告诉Qt这个类实现了哪些接口。在宏定义在实现插件的时候使用。例如:
class BasicToolsPlugin : public QObject,
public BrushInterface,
public ShapeInterface,
public FilterInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.PlugAndPaint.BrushInterface" FILE "basictools.json")
Q_INTERFACES(BrushInterface ShapeInterface FilterInterface)
public:
...
};
- Q_NAMESPACE
Q_NAMESPACE
宏用于将QMetaObject
功能添加到名称空间。Q_NAMESPACE
可以有Q_CLASSINFO
、Q_ENUM_NS
、Q_FLAG_NS,但不能有Q_ENUM
、Q_FLAG
、Q_PROPERTY
、Q_INVOKABLE
、信号或槽函数。
- Q_NAMESPACE_EXPORT(EXPORT_MACRO)
该宏的工作原理与Q_NAMESPACE
宏完全相同。但是,在名称空间中定义的外部staticMetaObject
变量是用提供的EXPORT_MACRO
限定符声明的。如果需要从动态库导出对象,该宏定义非常有用。
- Q_OBJECT
Q_OBJECT宏必须出现在类定义的私有部分中,该类定义声明自己的信号和槽函数,或者使用Qt元对象系统提供的其他支持。
- Q_PROPERTY(...)
此宏用于在继承QObject
的类中声明属性。属性的行为类似于类数据成员,但它们具有可通过元对象系统访问的其他特性。如下代码:
Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])
- Q_SIGNAL
这是一个额外的宏,允许我们将单个函数标记为信号。当使用不支持信号或Q_SIGNALS
组的第三方源代码解析器时,使用这个宏。
- Q_SLOT
这是一个额外的宏,它允许将单个函数标记为槽函数。当使用不支持槽函数或Q_SLOT
组的第三方源代码解析器时,使用这个宏。
三、信号和槽的连接机制注意事项
1、【自动连接(默认)】如果信号是在接收对象具有关联的线程中发出的,那么行为与直接连接相同。否则,行为与队列连接相同。
2、【直接连接】当信号发出时,槽函数被立即调用。槽函数在发射器的线程中执行,而发射器的线程不一定是接收器的线程。
3、【队列连接】当控制返回到接收方线程的事件循环时调用槽,槽函数在接收方的线程中执行。
4、【阻塞排队连接】槽被调用为排队连接,除非当前线程阻塞直到槽函数返回。
注意:使用该阻塞排队连接类型连接同一线程中的对象将导致死锁。
5、【唯一连接】与自动连接相同,但只在不复制现有连接的情况下才建立连接。也就是说,如果相同的信号已经为相同的对象连接到相同的槽,那么就不创建连接,并且connect()
返回false
。
连接类型可以通过向connect()
传递一个附加参数来指定。注意,如果事件循环运行在接收方的线程中,当发送方和接收方位于不同线程中时使用直接连接是不安全的,这与调用位于另一个线程中的对象上的函数是不安全的原因相同。
QObject::connect()本身是线程安全的。
对于队列连接,传递的参数必须是Qt元对象系统已知的类型,因为Qt需要复制参数,以便存储参数;如果参数类型不是Qt元对象系统已知的,使用队列连接,将获得错误提示信息,这时候则需要在创建连接之前,调用qRegisterMetaType()
向元对象系统注册该数据类型。
四、线程关联性
在Qt中,QObject实例具有线程相关性,或者可以理解成QObject
存在于某个线程中。当QObject接收到排队的信号或发布的事件时,槽函数或事件处理程序将在该对象所在的线程中运行,这一点很重要。
注意:如果一个QObject没有线程关联(也就是说,如果thread()
返回0),或者如果它存在于一个没有运行事件循环的线程中,那么它就不能接收排队的信号或发布的事件。
默认情况下,QObject实例存在于创建它的线程中,在实际开发中可使用thread()
查询对象的线程关联,并使用moveToThread()
更改对象的线程关联。
注意:所有QObject必须与它们的父对象生活在同一个线程中。除此之外,还需要知道:
(1)如果涉及的两个QObject位于不同的线程中,setParent()
将失败。
(2)当一个QObject被移动到另一个线程时,它的所有子线程也会被自动移动。
(3)如果QObject有一个父对象,moveToThread()
将失败。
(4)如果QObject是在QThread::run()
中创建的,它们不能成为QThread对象的子对象,因为QThread并不存在于调用QThread::run()
的线程中。
QObject的父子关系必须通过传递一个指向子构造函数的指针或调用setParent()
来设置。如果没有这个步骤,当调用moveToThread()时,对象的成员变量将保持在旧线程中。
『参考链接』:
【1】https://doc.qt.io/qt-5/threads-qobject.html#signals-and-slots-across-threads
【2】https://doc.qt.io/qt-5/qobject.
========== End