QT 中的元对象系统

作为一名十几年的 C++ 程序员,最近一段时间使用 QT 开发程序,发现 QT 中还是有许多值得深入理解的技术。QT 不仅仅是一个应用程序开发框架,还有一些对标准 C++ 的扩充。本文和大家一起探讨 QT 中的元对象系统。

在分析 QT 中的元对象系统之前,我们先回顾一下 C++ 中的 RTTI 机制。

C++的RTTI机制

RTTI 是 Runtime Type Identification 的缩写,字面意思是运行时类型识别。C++引入这个机制是为了让程序在运行时能根据基类的指针或引用来获得该指针或引用所指的对象的实际类型。

为什么需要在运行时根据基类的指针或引用来获得实际所指对象的类型呢?这又牵扯到面向对象编程中的多态。

C++ 中的多态是指通过一个基类指针或引用调用一个虚函数时,会根据具体对象的类型来调用该虚函数的不同实现。这样可以实现对象间的通信和转换,以及多态的行为和表现。举个例子:

#include <iostream>
#include <vector>
using namespace std;class Animal {
public:virtual void speak() = 0; // pure virtual function
};class Cat : public Animal {
public:void speak() override {cout << "Meow" << endl;}
};class Dog : public Animal {
public:void speak() override {cout << "Woof" << endl;}
};int main() {vector<Animal*> animals; // create a vector of Animal pointersanimals.push_back(new Cat()); // push a Cat object to the vectoranimals.push_back(new Dog()); // push a Dog object to the vectorfor (auto a : animals) { // use range-based for loop to iterate the vectora->speak(); // polymorphism, calls Cat::speak or Dog::speak}return 0;
}

在上面的代码中,假设是一个屋子里的动物,调用者不用关心具体是猫还是狗,直接调用共同的接口 speak 即可。

可以看出,多态的好处很明显,可以实现代码的抽象和封装,因为我们可以通过一个基类指针或引用来隐藏对象的具体类型和实现细节,而只暴露对象的公共接口。这在基于插件的系统架构中使用得非常广泛,比如 Visual Studio Code 就是靠插件支撑起来的。多态是一种非常有用的编程技巧,它可以让我们的代码更加复用和扩展,以及更加抽象和封装,提高我们的编程效率和质量。

但有的时候,我们可能需要在运行时,鉴别出目前的对象是猫或者狗,比如狗需要定时出去遛,猫不需要。一种解决方法是在基类 Animal 中定义一个 walk方法,并给一个默认实现:

class Animal {
public:virtual void speak() = 0; // pure virtual functionvirtual void walk() {}
};

在 Dog 类中重写 walk 方法,而在 Cat 类中直接使用缺省的空实现。但这种方法有个明显的问题,就是会引起类方法的膨胀,随着继承越来越多,会发现不同类之间有差别的方法越来越多,都塞进基类,会使得类臃肿不堪。

这个时候就可以请 RTTI 机制出场了。

C++ 的 RTTI 主要包括两个关键字:typeid 和 dynamic_cast。typeid 运算符,用于返回表达式的类型。dynamic_cast 运算符,用于将基类类型的指针或引用安全地转换为其派生类类型的指针或引用。

typeid 运算符返回一个对 type_info 对象的引用,其中,type_info 是在头文件中定义的一个类,这个类重载了 == 和 != 运算符,以便可以用于对类型进行比较。type_info 类的实现随厂商而异,但包含一个 name() 成员,该函数返回一个随实现而异的字符串,通常(但并非一定)是类的名称。例如,下面的代码可以判断 pg 指向的是否是 ClassName 类的对象:

#include <typeinfo>
...
if (typeid(Dog) == typeid(*a)) {// a points to a Dog object
}

dynamic_cast运算符可以用于指针和引用的类型转换,它的语法如下:

dynamic_cast<type>(expression)

其中,type 是目标类型,expression 是要转换的表达式。如果转换成功,dynamic_cast 返回一个指向目标类型的指针或引用;如果转换失败,dynamic_cast 返回一个空指针或引发一个 bad_cast 异常。dynamic_cast 的转换成功的条件是,expression 的类型必须是 type 的公有基类或者 type 的公有派生类,而且 expression 的类型必须包含虚函数,否则编译器会报错。例如,下面的代码可以将一个基类指针转换为一个派生类指针:

class Base {
public:virtual ~Base() {} // virtual destructor
};class Derived : public Base {
public:void foo() { ... } // derived class method
};Base *pb = new Derived(); // base class pointer points to a derived class object
Derived *pd = dynamic_cast<Derived *>(pb); // convert to derived class pointer
if (pd) {pd->foo(); // call derived class method
}

如果 pb 指向的不是一个 Derived 类的对象,那么 pd 将为 nullptr ,无法调用 foo() 方法。

这两个运算符都需要在编译器设置中开启 RTTI 的支持,否则可能会出现运行时错误。但是我们在编译程序时,通常是没有开启 RTTI 支持的。这是因为 RTTI 会增加程序的开销和复杂度,道理很简单,RTTI 需要在编译器和运行时系统中维护额外的类型信息。C++ 作为一个追求效率的语言,默认是没有开启 RTTI 的。

C++ 的 RTTI 机制的优点则是它是一种标准的、跨平台的、内置的类型识别机制,只要编译器支持,就可以使用。

QT 的元对象系统

QT 的元对象系统是一种在 C++ 语言之上的扩展,相较于 RTTI,更加强大,如信号和槽机制、运行时类型信息、动态属性系统等。QT 的元对象系统的核心是 QObject 类,它是所有可以利用元对象系统的类的基类。

还记得我们在 QT 中定义类,通常会继承自 QObject 或其子类,并且还会使用一个奇怪的宏Q_OBJECT。

QObject类定义了一些元数据,如类名、父类名、信号、槽、属性等,这些元数据可以在运行时被访问和操作。而为了启用元对象系统,需要在类声明的私有部分内使用 Q_OBJECT 宏,这个宏会告诉元对象编译器(moc)对这个类进行处理。

元对象编译器(moc)是一个工具,它会扫描源代码中包含 Q_OBJECT 宏的类,提取其中的元数据,并生成相应的元对象代码。这些代码被编译到最终的可执行文件中,供 QT 的运行时系统使用。运行时系统可以通过元对象表来访问和操作对象的元数据,实现信号和槽的连接、动态属性的添加和访问等功能。

信号和槽机制是 QT 的最大特色,它是一种对象间通信的方式。信号和槽都是成员函数,信号是当对象状态发生变化时发出的消息,槽是对信号做出响应的动作。信号和槽可以在不同的对象、不同的线程之间进行连接,实现松耦合的交互。信号和槽的声明和定义都需要使用特定的宏,如 signals、slots、emit等,这些宏会被 moc 转换为元对象代码。例如,下面的代码定义了一个自定义的信号和槽:

class MyWidget : public QWidget {Q_OBJECT // enable meta-object system
public:MyWidget(QWidget *parent = nullptr);...
signals: // declare signalsvoid mySignal(int value); // a custom signal
public slots: // declare slotsvoid mySlot(int value); // a custom slot
};MyWidget::MyWidget(QWidget *parent) : QWidget(parent) {...connect(this, &MyWidget::mySignal, this, &MyWidget::mySlot); // connect signal and slot...
}void MyWidget::mySlot(int value) {// do something when the signal is emitted...
}

动态属性系统是一种在运行时给对象添加和访问属性的方式。属性是对象的一些特征,如颜色、大小、位置等。动态属性系统允许在不修改类定义的情况下,给对象添加新的属性,或者修改已有属性的值。动态属性系统使用 QVariant 类来存储属性的值。

QVariant类是一种通用的数据类型,它可以存储各种类型的值,并在运行时进行类型转换。动态属性系统使用 setProperty() 和 property() 函数来设置和获取属性的值。例如,下面的代码给一个按钮对象添加了一个自定义的属性:

QPushButton *button = new QPushButton("OK");
button->setProperty("myProperty", 123); // add a custom property
int value = button->property("myProperty").toInt(); // get the property value

可以看出,QT 的元对象系统的优点还是比较明显的,它是一种基于 C++ 的、跨平台的、高级的类型识别机制,它可以让程序在运行时获取和操作对象的类型信息,实现对象间的无缝交互,以及在运行时动态地修改对象的行为和外观。

当然,缺点也比较明显,它需要在类声明中使用特殊的宏,对于有代码洁癖的人来说难以忍受。此外还需要使用一个额外的工具(moc)来生成元对象代码,这可能会增加程序的编译时间和复杂度,而且它可能会与一些 C++ 的特性不兼容,如多重继承、模板等。

小结

C++ 的 RTTI 机制和 QT 的元对象系统,这两种机制都可以在运行时获取和操作对象的类型信息,实现对象间的通信和转换。

  • RTTI 是一种标准的、安全的、内置的类型识别机制,它可以让程序在运行时识别出对象的类型,并进行安全的类型转换。它的缺点是,它会增加程序的开销和复杂度,而且它可能会破坏程序的封装性和抽象性,导致程序的设计不够优雅和灵活。

  • 元对象系统是一种高级的、灵活的、扩展的类型识别机制,它可以让程序在运行时获取和操作对象的类型信息,实现对象间的无缝交互,以及在运行时动态地修改对象的行为和外观。它的缺点是,它需要在类声明中使用特殊的宏,以及使用一个额外的工具(moc)来生成元对象代码,这可能会增加程序的编译时间和复杂度,而且它可能会与一些C++的特性不兼容,如多重继承、模板等。

  • RTTI 和元对象系统都有各自的优缺点,它们适用于不同的场景和需求。一般来说,如果我们只需要进行简单的类型识别和转换,而且不需要使用信号和槽、动态属性等功能,那么我们可以使用 RTTI 。如果我们需要进行复杂的类型识别和转换,而且需要使用信号和槽、动态属性等功能,那么我们可以使用元对象系统。

当然,如果要使用 QT 的元对象系统,势必需要把 QT 整套框架引入。这里对 C++ 和 QT 的初学者和爱好者提供一些有用的信息和参考,希望对大家有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/165120.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

<JavaEE> 什么是线程(Thread)?进程和线程有什么区别?

目录 一、线程&#xff08;Thread&#xff09;的概念 二、线程存在的意义 2.1 并发编程 2.2 比进程更“轻量” 三、使用线程时应该注意 四、进程和线程的区别 五、Java中的线程和操作系统中的线程是不同的概念 六、多线程编程 一、线程&#xff08;Thread&#xff09;的…

蓝桥杯官网练习题(奇怪的数列)

题目描述 从 X 星截获一份电码&#xff0c;是一些数字&#xff0c;如下&#xff1a; 13 1113 3113132113 1113122113 ⋯ YY 博士经彻夜研究&#xff0c;发现了规律&#xff1a; 第一行的数字随便是什么&#xff0c;以后每一行都是对上一行"读出来" 比如第 2…

图神经网络的数学原理总结

图深度学习(Graph Deep Learning) 多年来一直在加速发展。许多现实生活问题使GDL成为万能工具&#xff1a;在社交媒体、药物发现、芯片植入、预测、生物信息学等方面都显示出了很大的前景。 本文将流行的图神经网络及其数学细微差别的进行详细的梳理和解释&#xff0c;图深度学…

Linux中flask项目开启https访问

1.下载阿里云免费证书 2.项目添加https配置 3.服务器开启https访问 3.1 重新安装OpenSSL 3.2.重新安装Python 上一次已经讲过Linux安装部署Python: Linux安装Python3.10与部署flask项目实战详细记录,今天记录一下Python项目如何支持https访问…

《计算机中的程序》(灵魂六问)

目录 1、程序是什么&#xff1f; 2、程序是由什么组成的&#xff1f; 3、什么是机器语言&#xff1f; 4、正在运行的程序存储在什么位置&#xff1f; 5、什么是内存地址&#xff1f; 6、计算机的构成元件中&#xff0c;负责程序的解释和运行是哪个&#xff1f; 1、程序是什…

性能相关的闪存特性

一、多Plane操作 上章提到若干个Plane组成Die或者叫LUN,即一个Die上有多个Plane 每次进行写操作时&#xff0c;控制器先将数据写入页缓存中&#xff0c;等同一个Die上另一个Plane也写数据的时候&#xff0c;再同时写入&#xff0c;原来单独操作一个Plane的时间变成了可以同时做…

Springmvc实现增删改差

一、包结构 二、各层代码 (1)数据User public class User {private Integer id;private String userName;private String note;public User() {super();}public User(Integer i, String userName, String note) {super();this.id i;this.userName userName;this.note note;…

Qt实现自定义IP地址输入控件(百分百还原Windows 10网络地址输入框)

在开发网络相关的程序时,我们经常需要输入IP地址,例如源地址和目标地址。Qt提供了一些基础的控件,如QLineEdit,但是它们并不能满足我们对IP地址输入的要求,例如限制输入的格式、自动跳转到下一个输入框、处理回车和退格键等。因此,我们需要自己编写一个自定义的IP地址输入…

AI AIgents时代- Autogen

由微软开发的 Autogen 是一个新的 Agents 项目&#xff0c;刚一上线就登上GitHub热榜&#xff0c;狂揽11k星✨✨✨ 项目地址&#xff1a;https://github.com/microsoft/autogen Autogen 允许你根据需要创建任意数量的Agents&#xff0c;并让它们协同工作以执行任务。它的独特…

外观模式 (Facade Pattern)

定义&#xff1a; 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过提供一个统一的高层接口来简化复杂子系统或库的访问。这种模式的关键在于&#xff0c;它创建了一个外观类&#xff0c;这个类封装了对子系统的一系列复杂交互&#xf…

【赠书第7期】从零基础到精通Flutter开发

文章目录 前言 1 安装Flutter和Dart 2 了解Flutter的基础概念 2.1 Widget 2.2 MaterialApp和Scaffold 2.3 Hot Reload 3 编写你的第一个Flutter应用 3.1 创建一个Flutter项目 3.2 修改默认页面 3.3 添加交互 4 深入学习Flutter高级特性 4.1 路由和导航 4.2 状态管…

python之TCP的网络应用程序开发

文章目录 版权声明python3编码转换socket类的使用创建Socket对象Socket对象常用方法和参数使用示例服务器端代码客户端代码 TCP客户端程序开发流程TCP服务端程序开发流程TCP网络应用程序注意点socket之send和recv原理剖析send原理剖析recv原理剖析send和recv原理剖析图 多任务版…

浅谈C#在unity应用中的工厂模式

文章目录 前言简单工厂模式工厂方法模式抽象工厂模式Unity实战 前言 工厂模式是一种创建型设计模式&#xff0c;它提供了一种将对象的实例化过程封装起来的方法&#xff0c;使得客户端代码不必直接依赖于具体类。这有助于降低代码的耦合度&#xff0c;提高代码的可维护性和可扩…

Python项目打包

Python项目如何打包&#xff1f; 本指南总结了Python项目打包的最佳实践&#xff0c;主要涉及代码的打包和分发&#xff0c;以及环境和依赖的管理。 0. 一般项目清单 源代码&#xff08;可使用git托管&#xff09;数据包&#xff08;可使用DVC托管&#xff09;Docker环境镜像…

Python进行threading多线程编程及高级并发处理机制

threading 模块是 Python 中用于进行多线程编程的标准库之一。通过 threading 模块&#xff0c;你可以创建和管理线程&#xff0c;使得程序能够并发执行多个任务。以下是一些基本的 threading 模块的用法&#xff1a; 1. 创建线程&#xff1a; 使用 threading.Thread 类可以创…

在两个java项目中实现Redis的发布订阅模式

如何在两个java项目中实现Redis的发布订阅模式&#xff1f; 1. Redis简介2. 发布订阅模式介绍3. 实现思路4. 代码实现及详细解释4.1. RedisUtil4.2. Publisher4.3. Subscriber4.4. 运行程序 目录&#xff1a; Redis简介发布订阅模式介绍实现思路代码实现及详细解释 1. Redis简…

HTB Napper WriteUp

Napper 2023年11月12日 14:58:35User Nmap ➜ Napper nmap -sCV -A -p- 10.10.11.240 --min-rate 10000 Starting Nmap 7.80 ( https://nmap.org ) at 2023-11-12 13:58 CST Nmap scan report for app.napper.htb (10.10.11.240) Host is up (0.15s latency). Not shown: …

gitee推荐-SAPI++

一下内容来自gitee。 SaaS-Apps-Engine: 智者|SAPI是多应用、多租户SaaS应用引擎&#xff0c;支持&#xff08;小程序/公众号/轻应用/企微/抖音/支付宝/百度&#xff09;等多平台应用。基于ThinkPHP6.1/8.0原生多应用模式开发&#xff0c;简洁、高效、易扩展。集成强大的权限控…

适用于电脑的5个免费文件恢复软件分享

适用于电脑的最佳免费文件恢复软件 任何计算机用户都可能经历过丢失重要文件的恐惧。重要数据的丢失可能会令人不安和沮丧&#xff0c;无论是由于不小心删除、计算机故障还是硬盘格式化造成的。幸运的是&#xff0c;在数字时代&#xff0c;您可以使用值得信赖的解决方案检索这些…

好工具|datamap,一个好用的地图可视化Excel插件,在Excel中实现地理编码、拾取坐标

在做VRP相关研究的时候&#xff0c;需要对地图数据做很多处理&#xff0c;比如地理编码&#xff0c;根据“重庆市沙坪坝区沙正街174号”这样的一个文本地址知道他的经纬度&#xff1b;再比如绘制一些散点图&#xff0c;根据某个位置的经纬度在地图上把它标注出来。还有有的时候…