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_COPYQ_DISABLE_MOVE组合在一起。

  • Q_DISABLE_MOVE(Class 5.13)

​ 禁止对给定类使用移动构造函数和移动赋值操作符。

  • Q_EMIT

​ 当希望使用第三方信号/槽函数机制来使用Qt信号和槽函数时,可以使用此宏替代emit关键字来发出信号。

  • Q_ENUM(...)

​ 这个宏用于向元对象系统注册一个枚举类型。该宏必须放在enum声明之后,且放在具有Q_OBJECTQ_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_CLASSINFOQ_ENUM_NS、Q_FLAG_NS,但不能有Q_ENUMQ_FLAGQ_PROPERTYQ_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】

【2】

 

========== End

 

 
posted @ 2024-04-13 22:11  lsgxeva  阅读(378)  评论(0编辑  收藏  举报