[Qt开发思想探幽]QObject、模板继承和多继承
- [Qt开发探幽]QObject、模板继承和多继承
- ***1. QObject为什么不允许模板继承:***
- ***2.如果需要使用QObject进行多继承的话,子对象引用的父类链至多只能含有一个QObject***
- ***3.如果使用模板类和QObject做多继承,编译不通过***
- 问题场景
[Qt开发探幽]QObject、模板继承和多继承
当我们在用Qt开发一个软件框架的时候,在一个正式一点的库或者框架中,我们不可避免地想要使用继承,但是可能当我们开发完一个模块后,会发现一些问题,比如说在编译的时候发现父类会编译不通过。
先说结论:
1.Qt的QObject不支持模板继承。
2.如果需要使用QObject进行多继承的话,子对象引用的父类链至多只能含有一个QObject
3.如果使用模板类和QObject做多继承,依然会编译不通过
1. QObject为什么不允许模板继承:
-
元对象系统的复杂性:元对象系统(Meta-Object System)是 Qt 的一个重要特性,它允许在运行时获取对象的元信息,如类名、属性、信号、槽等。这个系统需要在编译阶段生成额外的元信息,并且在运行时维护一个元对象表。这个系统在处理模板类时会变得复杂,因为模板类的实例化和使用涉及到编译期和运行时的多个步骤,而元对象系统的设计主要针对静态类型的类层次结构。
-
编译期类型信息:元对象系统需要在编译期生成一些额外的类型信息,用于支持信号槽和反射等功能。模板类的实例化和类型信息在编译期可能变得模糊不清,这会导致元对象系统难以正确处理模板类的元信息。
-
类型擦除:C++ 模板的一个特点是类型擦除,即编译器在模板实例化时会生成不同的代码,但生成的代码是针对特定的类型的,而不是模板本身。这种类型擦除使得在运行时获取模板类型信息变得复杂,而元对象系统需要在运行时获取类型信息。
-
信号槽连接的动态性:信号槽机制允许在运行时动态地连接和断开信号槽。这就要求在运行时能够准确地识别信号和槽的参数类型,以便进行参数匹配。模板类的参数类型在编译期可能不确定,这给信号槽的动态连接带来挑战。
所以在继承了QObject的模板类中,编译会导致QMetaObject找不到元对象而发生编译期报错,所以请不要让任何模板类继承QObject。
你可能会问了,那类似QList和QSharedPointer类为什么可以是模板类,而且也是Qt的东西呢?这恰好是Qt这个库取巧的地方:所有的模板类都是容器,也就是说他们这个模板类中允许装入任何它们想要的东西。或者换句话说,Qt中所有的模板类,都不是QObject的子类,不然的话可以直接看头文件:
也就是说,Qt内部其实自己也是遵循这个规则的:请不要让任何模板类继承QObject。
2.如果需要使用QObject进行多继承的话,子对象引用的父类链至多只能含有一个QObject
QObject有一个很重要的特点,就是不支持拷贝。
-
在 Qt 的多继承体系中,只有一个类可以拥有 QObject 功能,这个类必须是多继承链中的第一个类。QObject 类为了支持元对象系统、信号槽机制以及其他与运行时类型信息相关的功能,要求继承 QObject 的类在构造函数中通过传递 parent 参数来指定父对象。这个父对象就是用于建立对象之间关系的,例如在父对象析构时,它的子对象也会被析构。
-
由于 C++ 的多继承特性,当一个类需要继承多个类,而其中一个类是 QObject,为了保证 QObject 的正确功能,必须将 QObject 继承链放在多继承链的第一个位置。这是因为 QObject 继承链在构造和析构过程中有一些特殊的操作,需要在第一个类中执行。
3.如果使用模板类和QObject做多继承,编译不通过
这是因为 QObject 类需要在构造和析构过程中执行特殊的操作,以支持元对象系统、信号槽机制和其他与运行时类型信息相关的功能。而模板类的继承可能会干扰这些特殊操作,导致编译错误或者运行时问题。
QObject 的特殊操作需要在构造函数中执行,并且这些操作需要基于类的实际类型来进行。而模板类在编译时才会实例化,因此编译器在处理模板类时无法获得完整的类型信息,从而无法保证 QObject 的特殊操作能够正确地执行。
为了避免这些问题,Qt 推荐将 QObject 放在继承链的第一个位置,以确保 QObject 的特殊操作可以正确地执行。如果您的类需要多继承,可以考虑使用接口继承(纯虚函数)来实现所需的功能,以避免在多继承中使用 QObject 和模板类导致的问题。
问题场景
为什么我突然会想到这个问题呢,因为我确实遇到了,场景大概是这样的:
我现在在开发一个软件框架。我现在这个框架内的所有对象都需要包含一些信号和槽(这是为了统一上下信息的交换),我们管这个最底层的东西叫TSG_Caller,理论上我这个框架内所有的东西都应该要继承到这个TSG_Caller中,方便我进行管理:
它实际上只有一些信号和槽,我希望任何对象都能通过注册,在kernel内统一地操作这些信号与槽,不仅仅是一些细分的类,大类也要提供这些东西。
当我开发到一定程度之后,我细化到对某个设备的操控。于是我写了一个模板类,用来初始化一些模板类的基本类型。我希望不同的设备有不同的输入参数类型,于是我们就有了如下的设备模板类:
于是这里就出现问题了:QObject和模板类的继承兼容不能说不好,只能说基本完全不允许使用。当然了,这也是可以理解的,毕竟MetaObject这么强大的功能不能支持模板也是理所当然的事。
至于解决方法,那就只能改造这个设备类了,不能用模板类了,而是只能采用通用的输入类型。我这里因为输入的参数类型有限而且都是明文,所以我这里将所有的输入和输出都改成QString传输json字符串,当然了输入的字符串也要提供给外部一个判断的函数,以免外部输入错误。
修改后的接口如下:
不知道有没有更好的办法,也许有,但是目前我只想到这种比较简单的方法