QT 中的元对象系统(三):QObject深入理解

目录

1.简介

2.特性

2.1.对象树与内存管理

2.2.信号与槽机制

2.3.事件处理

2.4.属性系统

2.4.1.Q_PROPERTY配置的属性

2.4.2.动态属性

2.4.3.实现原理

2.5.国际化支持

[2.6. 定时器支持](#2.6. 定时器支持)

3.类设计(q和d指针)

4.总结


1.简介

QObject这个 class 是 QT 对象模型的核心,它是所有 Qt 对象的基类。它为对象间通信(信号与槽机制)、事件处理、定时器支持以及对象树管理等功能提供了基础。

QObject 类的实现文件一共有四个:

  1. qobject.h,QObject class 的基本定义,也是我们一般定义一个类的头文件。

  2. qobject.cpp,QObject class 的实现代码基本上都在这个文件。

  3. qobjectdefs.h,这个文件中最重要的东西就是定义了 QMetaObject class,这个class是为了实现 signal、slot、properties,的核心部分。

  4. qobject_p.h,这个文件中的 code 是辅助实现QObject class 的,这里面最重要的东西是定义了一个 QObjectPrivate 类来存储 QOjbect 对象的成员数据。

2.特性

2.1.对象树与内存管理

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

总结:

  • QObject 支持对象树的概念,即父对象自动管理子对象的生命周期。
  • 当父对象被销毁时,其所有子对象也会自动被销毁。

2.2.信号与槽机制

QT 中的元对象系统(一):元对象和元数据-CSDN博客
QT 中的元对象系统(二):元对象实现原理QMetaObject_public: static struct qmetaobject const qcustomplo-CSDN博客

QT框架里面最大的特色就是在C++的基础上增加了元对象系统(Meta-Object System),而元对象系统里面最重要的内容就是信号与槽机制,这个机制是在C++语法的基础上实现的,使用了函数、函数指针、回调函数等概念。当然与我们自己去写函数所不同的是槽与信号机制会自动帮我们生成部分代码,比如我们写的信号函数就不需要写它的实现部分,这是因为在我们编译程序的时候,编译器会自动生成这一部分代码,当我们调用connect函数的时候,系统会自动将信号函数与槽函数相连接,于是当我们调用信号函数的时候,系统就会自动回调槽函数,不管你是在同一线程下调用或者在不同线程下调用,系统都会自动评估,并在合理的时候触发函数,以此来保证线程的安全。信号与槽机制是线程安全的,这可以使得我们在调用的时候不用再额外的增加过多保证线程同步的代码,为了实现元对象系统,QT把所有相关实现写在了QObject类中,所以当你想使用元对象系统的时候,你所写的类需要继承自QObject,包括QT自带的所有类都是继承自QObject。

cpp 复制代码
    static QMetaObject::Connection connect(const QObject *sender, const char *signal,
                        const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);

    static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,
                        const QObject *receiver, const QMetaMethod &method,
                        Qt::ConnectionType type = Qt::AutoConnection);

    inline QMetaObject::Connection connect(const QObject *sender, const char *signal,
                        const char *member, Qt::ConnectionType type = Qt::AutoConnection) const;

#ifdef Q_CLANG_QDOC
    template<typename PointerToMemberFunction>
    static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection);
    template<typename PointerToMemberFunction, typename Functor>
    static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor);
    template<typename PointerToMemberFunction, typename Functor>
    static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection);
#else
    //Connect a signal to a pointer to qobject member function
    template <typename Func1, typename Func2>
    static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
                                     const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
                                     Qt::ConnectionType type = Qt::AutoConnection)
    {
        typedef QtPrivate::FunctionPointer<Func1> SignalType;
        typedef QtPrivate::FunctionPointer<Func2> SlotType;

        Q_STATIC_ASSERT_X(QtPrivate::HasQ_OBJECT_Macro<typename SignalType::Object>::Value,
                          "No Q_OBJECT in the class with the signal");

        //compilation error if the arguments does not match.
        Q_STATIC_ASSERT_X(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount),
                          "The slot requires more arguments than the signal provides.");
        Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments<typename SignalType::Arguments, typename SlotType::Arguments>::value),
                          "Signal and slot arguments are not compatible.");
        Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<typename SlotType::ReturnType, typename SignalType::ReturnType>::value),
                          "Return type of the slot is not compatible with the return type of the signal.");

        const int *types = nullptr;
        if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
            types = QtPrivate::ConnectionTypes<typename SignalType::Arguments>::types();

        return connectImpl(sender, reinterpret_cast<void **>(&signal),
                           receiver, reinterpret_cast<void **>(&slot),
                           new QtPrivate::QSlotObject<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
                                           typename SignalType::ReturnType>(slot),
                            type, types, &SignalType::Object::staticMetaObject);
    }

    //connect to a function pointer  (not a member)
    template <typename Func1, typename Func2>
    static inline typename std::enable_if<int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0, QMetaObject::Connection>::type
            connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot)
    {
        return connect(sender, signal, sender, slot, Qt::DirectConnection);
    }

    //connect to a function pointer  (not a member)
    template <typename Func1, typename Func2>
    static inline typename std::enable_if<int(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0 &&
                                          !QtPrivate::FunctionPointer<Func2>::IsPointerToMemberFunction, QMetaObject::Connection>::type
            connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const QObject *context, Func2 slot,
                    Qt::ConnectionType type = Qt::AutoConnection)
    {
        typedef QtPrivate::FunctionPointer<Func1> SignalType;
        typedef QtPrivate::FunctionPointer<Func2> SlotType;

        Q_STATIC_ASSERT_X(QtPrivate::HasQ_OBJECT_Macro<typename SignalType::Object>::Value,
                          "No Q_OBJECT in the class with the signal");

        //compilation error if the arguments does not match.
        Q_STATIC_ASSERT_X(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount),
                          "The slot requires more arguments than the signal provides.");
        Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments<typename SignalType::Arguments, typename SlotType::Arguments>::value),
                          "Signal and slot arguments are not compatible.");
        Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<typename SlotType::ReturnType, typename SignalType::ReturnType>::value),
                          "Return type of the slot is not compatible with the return type of the signal.");

        const int *types = nullptr;
        if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
            types = QtPrivate::ConnectionTypes<typename SignalType::Arguments>::types();

        return connectImpl(sender, reinterpret_cast<void **>(&signal), context, nullptr,
                           new QtPrivate::QStaticSlotObject<Func2,
                                                 typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
                                                 typename SignalType::ReturnType>(slot),
                           type, types, &SignalType::Object::staticMetaObject);
    }

    //connect to a functor
    template <typename Func1, typename Func2>
    static inline typename std::enable_if<QtPrivate::FunctionPointer<Func2>::ArgumentCount == -1, QMetaObject::Connection>::type
            connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot)
    {
        return connect(sender, signal, sender, std::move(slot), Qt::DirectConnection);
    }

    //connect to a functor, with a "context" object defining in which event loop is going to be executed
    template <typename Func1, typename Func2>
    static inline typename std::enable_if<QtPrivate::FunctionPointer<Func2>::ArgumentCount == -1, QMetaObject::Connection>::type
            connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const QObject *context, Func2 slot,
                    Qt::ConnectionType type = Qt::AutoConnection)
    {
        typedef QtPrivate::FunctionPointer<Func1> SignalType;
        const int FunctorArgumentCount = QtPrivate::ComputeFunctorArgumentCount<Func2 , typename SignalType::Arguments>::Value;

        Q_STATIC_ASSERT_X((FunctorArgumentCount >= 0),
                          "Signal and slot arguments are not compatible.");
        const int SlotArgumentCount = (FunctorArgumentCount >= 0) ? FunctorArgumentCount : 0;
        typedef typename QtPrivate::FunctorReturnType<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotArgumentCount>::Value>::Value SlotReturnType;

        Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<SlotReturnType, typename SignalType::ReturnType>::value),
                          "Return type of the slot is not compatible with the return type of the signal.");

        Q_STATIC_ASSERT_X(QtPrivate::HasQ_OBJECT_Macro<typename SignalType::Object>::Value,
                          "No Q_OBJECT in the class with the signal");

        const int *types = nullptr;
        if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
            types = QtPrivate::ConnectionTypes<typename SignalType::Arguments>::types();

        return connectImpl(sender, reinterpret_cast<void **>(&signal), context, nullptr,
                           new QtPrivate::QFunctorSlotObject<Func2, SlotArgumentCount,
                                typename QtPrivate::List_Left<typename SignalType::Arguments, SlotArgumentCount>::Value,
                                typename SignalType::ReturnType>(std::move(slot)),
                           type, types, &SignalType::Object::staticMetaObject);
    }
#endif //Q_CLANG_QDOC

    static bool disconnect(const QObject *sender, const char *signal,
                           const QObject *receiver, const char *member);
    static bool disconnect(const QObject *sender, const QMetaMethod &signal,
                           const QObject *receiver, const QMetaMethod &member);
    inline bool disconnect(const char *signal = nullptr,
                           const QObject *receiver = nullptr, const char *member = nullptr) const
        { return disconnect(this, signal, receiver, member); }
    inline bool disconnect(const QObject *receiver, const char *member = nullptr) const
        { return disconnect(this, nullptr, receiver, member); }
    static bool disconnect(const QMetaObject::Connection &);

#ifdef Q_CLANG_QDOC
    template<typename PointerToMemberFunction>
    static bool disconnect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method);
#else
    template <typename Func1, typename Func2>
    static inline bool disconnect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
                                  const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot)
    {
        typedef QtPrivate::FunctionPointer<Func1> SignalType;
        typedef QtPrivate::FunctionPointer<Func2> SlotType;

        Q_STATIC_ASSERT_X(QtPrivate::HasQ_OBJECT_Macro<typename SignalType::Object>::Value,
                          "No Q_OBJECT in the class with the signal");

        //compilation error if the arguments does not match.
        Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments<typename SignalType::Arguments, typename SlotType::Arguments>::value),
                          "Signal and slot arguments are not compatible.");

        return disconnectImpl(sender, reinterpret_cast<void **>(&signal), receiver, reinterpret_cast<void **>(&slot),
                              &SignalType::Object::staticMetaObject);
    }
    template <typename Func1>
    static inline bool disconnect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
                                  const QObject *receiver, void **zero)
    {
        // This is the overload for when one wish to disconnect a signal from any slot. (slot=nullptr)
        // Since the function template parameter cannot be deduced from '0', we use a
        // dummy void ** parameter that must be equal to 0
        Q_ASSERT(!zero);
        typedef QtPrivate::FunctionPointer<Func1> SignalType;
        return disconnectImpl(sender, reinterpret_cast<void **>(&signal), receiver, zero,
                              &SignalType::Object::staticMetaObject);
    }

在Qt5版本上对槽函数的支持也是多种多样的,其中包括C语言函数、类函数、仿函数、lambda表达式等等。

C++可调用Callable类型的总结_c++ callable-CSDN博客

信号与槽的连接方式也是多种多样的,如下图所示:

cpp 复制代码
enum ConnectionType {
        AutoConnection,
        DirectConnection,
        QueuedConnection,
        BlockingQueuedConnection,
        UniqueConnection =  0x80
    };

它们的实现方式和区别将在后面讲解。

总结:

  • QObject 是实现 Qt 中信号 (signals) 和槽 (slots) 机制的基础。
  • 信号和槽是一种类型安全的回调机制,用于对象之间的通信。
  • 使用 Q_OBJECT 宏可以启用信号和槽功能。

2.3.事件处理

QObject可以通过event()接收事件并且过滤其它对象的事件。详细情况请参考installEventFilter()和eventFilter()。

cpp 复制代码
virtual bool event(QEvent *event);
virtual bool eventFilter(QObject *watched, QEvent *event);

...

void installEventFilter(QObject *filterObj);
void removeEventFilter(QObject *obj);

示例如下:

事件过滤是另一个常见的应用场景。通过重写QObject的eventFilter函数,我们可以在事件到达目标对象之前对其进行拦截和处理。

cpp 复制代码
// 示例:使用事件过滤
bool MyClass::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == targetObject && event->type() == QEvent::KeyPress) {
        // 拦截并处理键盘事件
        return true;
    }
    return false;
}

在这个示例中,MyClass通过eventFilter函数拦截了目标对象targetObject的键盘事件。

总结:

  • QObject 提供了事件处理机制,允许子类重写 event() 方法来处理事件。
  • 常见的事件包括鼠标事件、键盘事件等。

2.4.属性系统

2.4.1.Q_PROPERTY配置的属性

cpp 复制代码
Q_PROPERTY(type name
           READ getFunction
           [WRITE setFunction]
           [RESET resetFunction]
           [ NOTIFY notifySignal]
           [ DESIGNABLE bool ]
           [ SCRIPTABLE bool ]
           [ STORED bool ]
           [ USER bool ]
           [ CONSTANT ]
           [ FINAL ])

参数说明:

type:属性的数据类型。

name:属性的名称。

READ getFunction:获取属性值的函数。

WRITE setFunction(可选):设置属性值的函数。

RESET resetFunction(可选):重置属性值的函数。

NOTIFY notifySignal(可选):当属性值发生变化时发出的通知信号。

DESIGNABLE bool(可选):是否可以在设计模式下访问该属性(如 Qt Designer)。

SCRIPTABLE bool(可选):是否可以通过脚本访问该属性。

STORED bool(可选):属性值是否存储在对象的状态中(如果为 false,则表示该属性是派生的)。

USER bool(可选):是否是用户属性(主要用于 GUI 系统)。

CONSTANT(可选):表示该属性是一个常量,不能被修改。

FINAL(可选):表示该属性不能被子类覆盖。

Q_PROPERTY 是 Qt 框架中的一个宏,用于定义类的属性(property)。通过 Q_PROPERTY 宏,可以将类的成员变量暴露为属性,并提供对这些属性的访问、修改和通知机制。这是 Qt 的元对象系统(Meta-Object System)的一部分;实际上我们在做界面设计的时候经常会用到属性,比如修改Label的显示内容,需要用到Text属性,修改窗体长宽等等,在你做界面设计的时候,属性编辑框里面所显示的就是当前对象的所有属性,而这些属性的定义就是用上面的宏来定义的。实际上属性和变量是有点相似的,都是读值和写值的功能,那为什么不直接对变量操作就好了?虽然看起来相似,但是还是有不同点,第一属性可以定义为可读写的,也可以定义为只读的,比如有些数据我们只在类的内部做修改不允许在外部做修改,但是有时候又需要在外部查看这个值,就可以设置为只读属性,而变量是做不到这点的,你把变量放在public部分,那么这个变量就可以在任何地方被修改,这就破坏了类的封装性。第二属性可以定义信号,当属性变化的时候触发信号,这样我们可以在信号触发时做一些工作,比如当你设置LineEdit为readonly时,你会发现输入框的背景颜色被改变了,这就可以通过属性变化的信号来处理。

实例一:以下是一个简单的 Q_PROPERTY 使用示例:

cpp 复制代码
#include <QObject>
#include <QDebug>

class MyClass : public QObject {
    Q_OBJECT

    // 定义一个整数属性 "value"
    Q_PROPERTY(int value READ getValue WRITE setValue NOTIFY valueChanged)

public:
    explicit MyClass(QObject *parent = nullptr) : QObject(parent), m_value(0) {}

    int getValue() const { return m_value; } // 获取属性值
    void setValue(int val) {                // 设置属性值
        if (m_value != val) {
            m_value = val;
            emit valueChanged(m_value);     // 发出通知信号
        }
    }

signals:
    void valueChanged(int newValue); // 属性变化的通知信号

private:
    int m_value;
};

int main(int argc, char *argv[]) {
    MyClass obj;

    // 动态访问属性
    qDebug() << "Initial value:" << obj.property("value").toInt();

    // 修改属性
    obj.setValue(42);

    // 再次访问属性
    qDebug() << "Updated value:" << obj.property("value").toInt();

    return 0;
}

#include "main.moc"

示例二: QML 中使用 Q_PROPERTY

cpp 复制代码
// 注册类到 QML
qmlRegisterType<MyClass>("MyModule", 1, 0, "MyClass");

// 在 QML 中使用
import MyModule 1.0

MyClass {
    id: myClass
    value: 10

    onValueChanged: console.log("Value changed to", value)
}

注意事项

  1. Q_OBJECT :使用 Q_PROPERTY 的类必须继承自 QObject 并包含 Q_OBJECT 宏。

  2. 类型限制 :属性的类型必须是 Qt 支持的元类型(Meta-Type),或者需要通过 qRegisterMetaType() 注册自定义类型。

  3. 性能:属性访问比直接访问成员变量稍慢,因为涉及到元对象系统的开销。

2.4.2.动态属性

继承QObject并使用Q_OBJECT宏后,你的类将能够使用动态属性。这是一种在运行时添加、修改或删除对象属性的机制。并通过 setProperty()property() 方法访问这些属性。与通过 Q_PROPERTY 宏定义的静态属性不同,动态属性不需要在编译时定义,完全可以在运行时创建和修改。

动态属性的基本操作

  1. 设置动态属性 :使用 QObject::setProperty(const char *name, const QVariant &value) 方法可以为对象设置一个动态属性。

  2. 获取动态属性 :使用 QObject::property(const char *name) 方法可以获取指定名称的动态属性值。

  3. 检查属性是否存在 :使用 QObject::dynamicPropertyNames() 方法可以获取对象的所有动态属性名称列表,从而判断某个属性是否存在。

  4. 删除动态属性:Qt 没有直接提供删除动态属性的方法,但可以通过重新设置为默认值或覆盖的方式来"清除"属性。

以下是一个简单的示例,展示如何使用动态属性:

cpp 复制代码
#include <QObject>
#include <QDebug>

int main(int argc, char *argv[]) {
    QObject obj;

    // 设置动态属性
    obj.setProperty("name", "MyObject");
    obj.setProperty("age", 42);

    // 获取动态属性
    qDebug() << "Name:" << obj.property("name").toString();
    qDebug() << "Age:" << obj.property("age").toInt();

    // 检查所有动态属性
    QList<QByteArray> propertyNames = obj.dynamicPropertyNames();
    qDebug() << "Dynamic Property Names:" << propertyNames;

    // 修改动态属性
    obj.setProperty("age", 43);
    qDebug() << "Updated Age:" << obj.property("age").toInt();

    return 0;
}

输出结果:

cpp 复制代码
Name: "MyObject"
Age: 42
Dynamic Property Names: ("name", "age")
Updated Age: 43

动态属性的特点

1.灵活性:动态属性可以在运行时动态添加、修改和访问,适合需要灵活扩展的对象。

2.类型安全 :动态属性的值以 QVariant 的形式存储,支持多种数据类型。

3.元对象系统支持:动态属性是 Qt 元对象系统的一部分,可以通过反射机制访问。

4.不支持通知机制 :动态属性没有内置的通知机制。如果需要属性变化的通知功能,建议使用 Q_PROPERTY 宏定义静态属性。

动态属性的应用场景

1.UI 属性扩展:在 GUI 应用程序中,可以为控件动态添加自定义属性,用于存储额外的信息。

2.配置管理:使用动态属性存储对象的运行时配置信息。

3.插件系统:在插件系统中,动态属性可以用来存储和传递插件相关的元数据。

2.4.3.实现原理

cpp 复制代码
//设置属性
bool setProperty(const char *name, const QVariant &value);
//获取属性
QVariant property(const char *name) const;
//获取所有动态属性
QList<QByteArray> dynamicPropertyNames() const;

查看一下setProperty的源码:

cpp 复制代码
bool QObject::setProperty(const char *name, const QVariant &value)
{
    Q_D(QObject);
    const QMetaObject* meta = metaObject();
    if (!name || !meta)
        return false;

    int id = meta->indexOfProperty(name);
    if (id < 0) {
        if (!d->extraData)
            d->extraData = new QObjectPrivate::ExtraData;

        const int idx = d->extraData->propertyNames.indexOf(name);

        if (!value.isValid()) {
            if (idx == -1)
                return false;
            d->extraData->propertyNames.removeAt(idx);
            d->extraData->propertyValues.removeAt(idx);
        } else {
            if (idx == -1) {
                d->extraData->propertyNames.append(name);
                d->extraData->propertyValues.append(value);
            } else {
                if (value.userType() == d->extraData->propertyValues.at(idx).userType()
                        && value == d->extraData->propertyValues.at(idx))
                    return false;
                d->extraData->propertyValues[idx] = value;
            }
        }

        QDynamicPropertyChangeEvent ev(name);
        QCoreApplication::sendEvent(this, &ev);

        return false;
    }
    QMetaProperty p = meta->property(id);
#ifndef QT_NO_DEBUG
    if (!p.isWritable())
        qWarning("%s::setProperty: Property \"%s\" invalid,"
                 " read-only or does not exist", metaObject()->className(), name);
#endif
    return p.write(this, value);
}

可以看到setProperty还是依赖于QObject的元系统的,首先判断属性名name是否为Q_PROPERTY设置的属性,如果是则根据元系统的实现方式,获取元属性的索引id,再写入值; 接着动态属性是在存在QObject的数据区中的propertyNames和propertyValues中,其实对动态属性的存取就是对propertyNames和propertyValues的操作。

cpp 复制代码
class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
    Q_DECLARE_PUBLIC(QObject)

public:
    struct ExtraData
    {
        ExtraData() {}
    #ifndef QT_NO_USERDATA
        QVector<QObjectUserData *> userData;
    #endif
        QList<QByteArray> propertyNames;   //动态属性名
        QVector<QVariant> propertyValues;  //动态属性值
        QVector<int> runningTimers;
        QList<QPointer<QObject> > eventFilters;
        QString objectName;
    };

    //...
};

2.5.国际化支持

cpp 复制代码
#if defined(QT_NO_TRANSLATION)
    static QString tr(const char *sourceText, const char * = nullptr, int = -1)
        { return QString::fromUtf8(sourceText); }
#if QT_DEPRECATED_SINCE(5, 0)
    QT_DEPRECATED static QString trUtf8(const char *sourceText, const char * = nullptr, int = -1)
        { return QString::fromUtf8(sourceText); }
#endif
  • QObject 提供了对翻译的支持,可以通过 tr() 函数实现多语言文本的翻译。

2.6. 定时器支持

cpp 复制代码
    int startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer);
#if QT_HAS_INCLUDE(<chrono>)
    Q_ALWAYS_INLINE
    int startTimer(std::chrono::milliseconds time, Qt::TimerType timerType = Qt::CoarseTimer)
    {
        return startTimer(int(time.count()), timerType);
    }
#endif
    void killTimer(int id);
  • QObject 可以通过 startTimer() 创建定时器,并在 timerEvent() 中处理定时器事件。

3.类设计(q和d指针)

C++惯用法之Pimpl

我们知道,在C++中,几乎每一个类(class)中都需要有一些类的成员变量(class member variable),在通常情况下的做法如下:

cpp 复制代码
class Person
{
private:
    std::string mszName; // 姓名
    bool mbSex;    // 性别
    int mnAge;     // 年龄
};

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

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

为了更容易理解QT是如何定义类成员变量的,我们先说一下QT 2.x 版本中的类成员变量定义方法,因为在 2.x 中的方法非常容易理解。然后在介绍 QT 4.4 中的类成员变量定义方法。

QT 2.x 中的方法

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

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 中,然后定义了一些相关的方法来存取,好了,让我们进入具体的代码吧。

cpp 复制代码
// 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)

cpp 复制代码
#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)展开后,就是下面的代码:

cpp 复制代码
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的定义:

cpp 复制代码
class QObjectPrivate : public QObjectData
{
	Q_DECLARE_PUBLIC(QObject)

public:

	QObjectPrivate(int version = QObjectPrivateVersion);
	virtual ~QObjectPrivate();
	// 省略
}

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

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

cpp 复制代码
// 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 复制代码
new QObject(*new QObjectPrivate, NULL);  

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

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

cpp 复制代码
class QWidget : public QObject, public QPaintDevice  
{  
    Q_OBJECT  
    Q_DECLARE_PRIVATE(QWidget)  
    // .....................  
}  

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

cpp 复制代码
QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)  
: QObject(*new QWidgetPrivate, 0), QPaintDevice()  
{  
    d_func()->init(parent, f);  
}  

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

再看QWidgetPrivate这个class的定义:

cpp 复制代码
class QWidgetPrivate : public QObjectPrivate  
{  
    Q_DECLARE_PUBLIC(QWidget)  
    // .....................  
};  

好了,这就把所有的事情都串联起来了。

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

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

cpp 复制代码
inline QWidgetPrivate* d_func() { return reinterpret_cast<QWidgetPrivate *>(d_ptr); }  
inline const QWidgetPrivate* d_func() const  
{ return reinterpret_cast<const QWidgetPrivate *>(d_ptr); } \  
friend class QWidgetPrivate;  

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

4.总结

QObject 是 Qt 框架的核心组件,提供了丰富的功能和强大的灵活性,适用于从简单到复杂的各种应用程序开发需求。它的主要优势包括:

  1. 对象间通信:通过信号与槽机制实现松耦合的设计。
  2. 内存管理:通过对象树自动管理资源。
  3. 事件驱动:支持事件处理和扩展。
  4. 国际化支持:简化多语言应用的开发。
  5. 动态扩展性:支持动态属性和定时器。

在实际开发中,QObject 的功能被广泛应用于 GUI 开发、网络编程、嵌入式开发等多个领域。掌握 QObject 的核心特性对于高效使用 Qt 框架至关重要。

相关推荐
唐静蕴2 分钟前
Kotlin语言的安全开发
开发语言·后端·golang
LabVIEW开发10 分钟前
LabVIEW 调用 Python 函数
开发语言·python·labview
老哥不老18 分钟前
从零掌握 Playwright:用 Python 玩转现代浏览器自动化
开发语言·python·自动化
yngsqq28 分钟前
批量改CAD图层颜色——CAD c#二次开发
开发语言·数据库·c#
Hello.Reader32 分钟前
迭代器介绍与使用(四十一)
开发语言·c++
储悠然43 分钟前
Lisp语言的物联网数据分析
开发语言·后端·golang
东方珵蕴1 小时前
COBOL语言的折线图
开发语言·后端·golang
知识中的海王1 小时前
js逆向入门图灵爬虫练习平台 第四题学习
开发语言·前端·javascript
光算科技1 小时前
无限滚动(Infinite Scroll)页面谷歌不收录!必须改回分页吗?
java·开发语言
重生之我要成为代码大佬1 小时前
从零讲透DFS-深度优先搜索-2(排序与组合)
开发语言·python·算法·深度优先遍历