QT元对象Meta-Object系统

文章目录

  • 内省
    • C++多态
    • C++ RTTI
    • 为什么QT采用元对象Meta-Object系统
  • 元对象系统(运行时类型识别)
    • QT元对象系统用途
      • 使用元对象系统需要满足三个条件
    • moc: Meta-Object Compiler元对象编译器
    • Q_OBJECT()宏
      • 源码解析
    • 信号槽机制: 实例对象之间的通信
  • 动态属性系统:控件
    • 属性系统Q_PROPERTY
    • 使用方法
  • 对象模型
  • 对象树
  • 信号槽

QMetaObject::invokeMethod()

内省

型别内省(type intropection):运行期间检查对象型别

基于内省机制,可以列出对象的方法和属性列表,并且能够获取有关对象的所有信息,如参数类型。如果没有内省机制,QtScript和 QML是难以实现的。

C++多态

C++ RTTI

由于C++是静态类型语言,有关类的信息只在编译期被使用,编译后就不再保留,因此程序运行时无法获取类的信息。这时就需要使用「运行期类型信息」,即RTTI(Run-Time Type Information):程序运行时保存对象类型信息

C++标准定义了dynamic_casttypeid两个关键字用于支持RTTI机制。

  • dynamic_cast:将指向基类对象的指针转换为指向派生类对象的指针,如果转换失败则返回NULL。
    • 判断一个对象具有哪些类型
      GrandFather *p; Son *son = dynamic_cast(p)
      
  • typeid:返回类型的名字
    • 只能返回类名

https://zhuanlan.zhihu.com/p/66425414

为什么QT采用元对象Meta-Object系统

  1. 完整的描述一个类型需要很多信息:类名字、父类、成员、哪些是public的、哪些是private的、哪些是protected的等等。一个工程项目可能包含成千上万个类,完整的保存这些信息会消耗大量的内存资源。为了节省内存,C++标准约定typeid只能返回类名。因此,仅靠dynamic_cast和typeid两个关键字提供的类型信息实在有限。即使仅提供有限的类型信息,RTTI的实现仍然耗费了很大的时间和存储空间,这就会降低程序的性能。
  2. 不同的编译器的实现不同,更别说提供RTTI功能的库千差万别。由此导致的最大问题就是程序的可移植性差,项目之间无法完美兼容。

元对象系统(运行时类型识别)

https://zhuanlan.zhihu.com/p/61303678

使用该系统的基类QObject所创建的派生类对象,可以在运行期获取该对象的类名、父类名、枚举类型以及有哪些成员变量、有哪些成员函数等信息。基于这些信息,Qt实现了强大的信号槽机制

Qt中的元对象系统全称Meta Object System,是一个基于标准C++的扩展,为Qt提供了信号与槽机制、实时类型信息、动态属性系统。元对象系统基于QObject类、Q_OBJECT宏、元对象编译器MOC实现。

QT元对象系统用途

Qt元对象系统的强大在于“即使编译器不支持RTTI,我们也能动态获取类型信息”。

QMetaObject::className()  // 返回类的名称

qobject_cast()相比dynamic_cast()强制转换安全得多,而且速度更快。因此,对于QObject派生类之间的转换,推荐使用qobject_cast()。

QObject *obj = new QWidget();
QWidget *widget = qobject_cast<Qwidget *>(obj);

元对象系统除了提供信号槽机制在对象间进行通讯的功能,还提供了如下功能:

  • QObject::metaObject() 方法
    • 获得与一个类相关联的 meta-object
  • QMetaObject::className() 方法
    • 在运行期间返回一个对象的类名,不需要本地C++编译器的RTTI(run-time type information)支持
  • QObject::inherits() 方法
    • 用来判断生成一个对象类是不是从一个特定的类继承出来,必须是在QObject类的直接或者间接派生类当中。
  • QObject::tr() and QObject::trUtf8()
    • 为软件的国际化翻译字符串
  • QObject::setProperty() and QObject::property()
    • 根据属性名动态的设置和获取属性值
  • 使用qobject_cast()方法在QObject类之间提供动态转换,qobject_cast()方法的功能类似于标准C++的dynamic_cast(),但qobject_cast()不需要RTTI的支持。

使用元对象系统需要满足三个条件

  • 只有QObject派生类才可以使用元对象系统特性。
  • 在类声明前使用Q_OBJECT()宏来开启元对象功能。
  • 使用Moc工具为每个QObject派生类提供实现代码。

moc: Meta-Object Compiler元对象编译器

Qt 程序在交由标准编译器编译之前,先要使用 moc 分析 C++ 源文件。

  • 如果它发现在一个头文件中包含了宏 Q_OBJECT,则会动态生成另外一个 C++ 源文件。这个源文件中包含了 Q_OBJECT 宏的实现代码。这个新的文件名字将会是原文件名前面加上 moc_ 构成。
  • 这个新的文件同样将进入编译系统,最终被链接到二进制代码中去。因此我们可以知道,这个新的文件不是“替换”掉旧的文件,而是与原文件一起参与编译,作为类的完整的一部分。
  • moc 的执行是在预处理器之前。因为预处理器执行之后,Q_OBJECT 宏就不存在了。
  • 如果你使用 qmake 的话,moc 调用会在生成的 makefile 中展现出来。从本质上来说,qmake 不过是一个 makefile 生成器,因此,最终执行还是通过 make 完成的。

Q_OBJECT()宏

定义在每一个类的私有数据段,用来启用元对象功能,比如动态属性、信号和槽。

在一个QObject类或者其派生类中,如果没有声明Q_OBJECT宏,那么类的metaobject对象不会被生成,类实例调用metaObject()返回的就是其父类的metaobject对象,导致的后果是从类的实例获得的元数据其实是父类的数据。因此类所定义和声明的信号和槽都不能使用,所以,任何从QObject继承出来的类,无论是否定义声明了信号、槽和属性,都应该声明Q_OBJECT 宏。(如果 A 继承了 QObject 并且定义了 Q_OBJECT,B 继承了 A 但没有定义 Q_OBJECT,C 继承了 B,则 C 的 QMetaObject::className() 函数将返回 A,而不是本身的名字。)

源码解析

在 qobjectdefs.h 里面,找到 Q_OBJECT 宏的定义:

#define Q_OBJECT \ 
public: \ 
    Q_OBJECT_CHECK \ 
    static const QMetaObject staticMetaObject; \ 
    Q_OBJECT_GETSTATICMETAOBJECT \ 
    virtual const QMetaObject *metaObject() const; \ 
    virtual void *qt_metacast(const char *); \ 
    QT_TR_FUNCTIONS \ 
    virtual int qt_metacall(QMetaObject::Call, int, void **); \ 
private: 

在调用moc后生成的 moc_xxx.cpp文件中,包含了上述object中的属性和函数。

  • virtual const QMetaObject *metaObject() const; 函数:返回 QMetaObject 元对象类的实例。通过它,你就获得了 Qt 类的反射的能力:获取本对象的类型之类,而这一切,都不需要 C++ 编译器的 RTTI 支持。
  • Qt qobject_case():类似 C++ 的 dynamic_cast() ,不需要 RTTI。

信号槽机制: 实例对象之间的通信

C++对象间的交互一般使用回调函数来实现。

  • 使用某对象时,用指针指向另一个对象的函数,这个函数就称为回调函数。

使用回调函数有个弊端,当某个对象被多个对象通信时,需要一个容器来存放多个对象的回调函数。维护这个容器使得代码编写效率低、扩展性弱。

动态属性系统:控件

Qt属性系统是独立于编译器和平台的。

  1. 在C++中是没有属性概念的,只有成员变量。因为面向对象的思想是抽象封装。
  2. 属性是类给外部展示的特性。而成员变量属于类的内部信息,直接暴漏出去就破坏了封装性。而属性将取值、赋值的细节进行了封装,外部只能使用它而不能控制它。

属性系统Q_PROPERTY

https://blog.csdn.net/u010168781/article/details/86562801

  • 基于元对象Meta-Object系统,需要用moc进行编译。因此在使用时,需要继承QObject类并添加宏Q_OBJECT
  • 属性除了具有类成员的功能外,还可以通过元对象系统访问,比如可以使用信号和槽机制。

使用方法

在继承QObject的类中,使用宏Q_PROPERTY()来注册属性。

Q_PROPERTY()宏定义编译期的静态属性,使用setProperty()函数动态添加属性。

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_PROPERTY(
Priority priority 
READ priority 
WRITE setPriority 
NOTIFY priorityChanged)
  1. type name 属性的类型和名字(类的数据成员)
  • 属性类型可以是QVariant支持的任何类型,也可以是用户定义的类型;
  1. READ 和 MEMBER 至少有一个
  • READ:如READ priority,则后面需要定义获取属性值的函数,该函数是const:Priority priority() const {}
  • MEMBER:如果一个属性不需要 READ ,但又想使用属性系统,可以使用MEMBER来注册,MEMBER后面是成员变量的名字。
    • WRITE 设置属性值,是可选的
  1. RESET:属性设置为默认值
    可选,该RESET函数必须返回void并且不带参数。

  2. NOTIFY:属性的值更改信号
    可选,NOTIFY 后面跟该类中已经定义的一个信号函数,只要该属性的值发生更改,就会发出该信号。这个信号函数必须采用零个或一个参数,该参数必须与属性的类型相同。

  3. REVISION int
    可选,版本信息(通常用于QML)。

  4. DESIGNABLE bool
    可选,表示属性是否能在GUI设计工具的属性编辑器中可见(例如,Qt Designer)。大多数属性是DESIGNABLE(默认为true)。

  5. SCRIPTABLE bool
    可选,SCRIPTABLE属性表示脚本引擎是否应该可以访问此属性(默认为true)

  6. STORED bool
    可选,该属性是单独存在还是从其他值中获取的。大部分是true,一个反例是QWidget::minimumWidth()的值从QWidget::minimumSize()中获取,因此它的STORED为false。

  7. USER bool
    可选,表示是否可以被用户所编辑。

  8. CONSTANT
    可选,CONSTANT表明属性值是常量,不可更改,因此不能有WRITE方法或NOTIFY信号。对于给定的对象实例,常量属性的READ方法每次调用时都必须返回相同的值。对于对象的不同实例,该常数值可以是不同的。

  9. FINAL
    可选,FINAL表示属性不会被派生类覆盖,在某些情况下,这可用于性能优化。

Qt核心剖析: moc

对象模型

对象树

信号槽

你可能感兴趣的:(C++\QT,QT元对象系统,QT属性系统)