在我继续一系列的Qt数据序列化文章之前,有一个相对重要的需要提及的话题,那就是:基于类名动态创建类对象的能力。

假定现在我们要创建一系列的形状,形状是一个抽象类,实际类是存储在一个列表中的各种各样的派生类:矩形、圆等等。在序列化期间,我们可以保存每一项的类名和对象数据,在反序列化(即加载数据)时,我们需要能够创建合适类实例的能力,这就是要用到一个对象工厂的地方。在支持反射的语言中,例如C#、Java,仅需要几行代码就可以从一个跟定的类名字符串获得一个类实例。但是在c++中没有这样的机制。

一个简单的解决方案是创建一个单独的函数,里面有一个大的switch块(或者一系列的switch块)来创建合适的类对象,尽管这种方法不雅观并且破坏了面向对象设计,但是大多数情况下是可以接受的。然而,当你有很多的类而且分散在应用程序的不同模块时,使用上述方法可能会变得难以管理,而且当应用程序有扩展的模块和动态加载的插件时,这就会变得更加困难。

另一个更优雅的解决方法是有一个不需要知道任何对象类型的工厂,而要通过工厂实例化的类必须使用某种内部图来先注册,这样,每个模块或插件可以独立的注册它们各自类。

QT有两种可以用来创建这样的工厂的机制,它们看起来相似,实际上有很大的差别:

QMetaType

construct()方法能够用来创建任何内建类型的实例,或者是通过Q_DECLARE_METATYPE宏指定的自定义类型。这是QVariant所要做的,用来内部封装自定义类型。然而这种机制是用来供变量类型使用的,也就是有默认构造和拷贝函数的类,但是对于抽象类对象是没有意义的,因为抽象类通常使用指针传递,并且拷贝构造通常被禁用。

QMetaObject

newInstance()方法可以用来创建任何一个从QObject派生下来的类的实例,仅有的条件是类的构造器必须通过Q_INVOKABLE修饰,来明确地声明。这和多态对象配合可以很好的工作,因为QObject类通常作为各种抽象类的基类。值得注意的是,从QT4开始,若没有额外的工作,仅仅依靠类名是不可能检索到QMetaObject的。

可以很容易的创建一个依赖于QMetaObject的对象工厂,这里有一种实现,不过这种解决方法也有一些缺点:
       构造器必须使用Q_InVOKABLE显示声明,以便能够访问QMetaObject;

没有在编译期检查是否存在合适的构造函数可以访问,或者参数类型是否正确,当你实际尝试创建实例时,仅仅会得到一个运行时警告,并返回空指针;

子类化QObject会增加每个对象实例的内存占用,当执行运行时类型检查时,通过QMetaObject进行的动态方法调用也会存在一些开销。

然而,创建一个可以创建任何类的自定义类工厂也不是难事,下面是一个适用于任何继承于QObject的类的创建工厂,如下为Foo.h类:

class Foo :public QObject
{Q_OBJECT
public:Foo(QObject*) {};
};
#include"Foo.h"
#include<QHash>class ObjectFactory
{
public:template<typename T>static void registerClass(){// 最后一个参数是函数指针,只有才调用时才需要传入参数constructors().insert(T::staticMetaObject.className(), &constructorHelper<T>);}static QObject* createObject(const QByteArray& className, QObject* parent = NULL){Constructor constructor = constructors().value(className);if (constructor == NULL)return NULL;return (*constructor)(parent); // constructor其实是 registerClass()函数中传入的函数指针}private:typedef QObject* (*Constructor)(QObject* parent);template<typename T>static QObject* constructorHelper(QObject* parent ){return new T(parent);}static QHash<QByteArray, Constructor>& constructors(){static QHash<QByteArray, Constructor> instance;return instance;}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);ObjectFactory::registerClass<Foo>();QObject* foo = ObjectFactory::createObject("Foo");return a.exec();
}

使用这种途径,不在需要使用Q_INVOKABLE声明构造器了,而且如果没有找到合适的构造器,只要这个类注册了,在constructorHelper()方法中就会报告一个编译错误。而且代码很容易使用:


  1. ObjectFactory::registerClass<Foo>();

  2. // ...

  3. QObject* foo = ObjectFactory::createObject( "Foo" );

同时也很容易修改这个代码,来适用于那些不从QObject继承的自定义抽象类,例如它可以使用任何传递给registerClass()方法或者自动从类的静态成员接收的类型的“Key”,而不是使用从OMetaObject接收的类名作为“Key”.根据需要还有一组不同的参数可以传递给构造函数。

QT 动态创建对象(第一种方法)相关推荐

  1. 利用Qt元对象技术防止工厂模式下代码臃肿问题,QT 动态创建对象(第2种方法)

    问题的提出: 近来要编写一个仿真液压.电力.机械的软件,如下为液压的: 可以看到液压图中很多液压元器件,这些元器件的id.名称等都是从json配置文件读取的,配置文件格式如下: {"Clas ...

  2. Qt5.9一个简单的多线程实例(类QThread)(第一种方法)

    Qt开启多线程,主要用到类QThread.有两种方法,第一种用一个类继承QThread,然后重新改写虚函数run().当要开启新线程时,只需要实例该类,然后调用函数start(),就可以开启一条多线程 ...

  3. Qt绑定UI界面和Qt类的四种方法

    1. Qt类头文件中 声明命名空间 namespace Ui { class Widget; } 声明UI指针对象 public:explicit Widget(QWidget *parent = 0 ...

  4. 【错误记录】NDK 导入外部 so 动态库报错 ( java.lang.UnsatisfiedLinkError | Android Studio 配置外部 so 动态库两种方法 )

    文章目录 一.报错信息 二.解决方案 ( Android Studio 配置外部 so 动态库两种方法 ) 1.jniLibs 目录存放 2.libs 目录存放 一.报错信息 外部引用 so 动态库 ...

  5. Javascript创建对象几种方法解析

    Javascript创建对象几种方法解析 Javascript面向对象编程一直是面试中的重点,将自己的理解整理如下,主要参考<Javascript高级程序设计 第三版>,欢迎批评指正. 通 ...

  6. 【C语言】求一千以内的素数 第一种方法

    第一种方法:平常思维(人们第一印象会想到的) //什么是素数? -- 除了1和本身之外不能被其他数整除的数 #include "stdio.h"int main(){int n,j ...

  7. JAVASE基础模块三十五( 线程 线程创建的第一种方法 以及线程的一些方法)

    JAVASE基础模块三十五( 线程 线程创建的第一种方法 以及线程的一些方法) 线程 首先要清楚的是 线程依赖于进程 进程 是 正在运行的应用程序 一个正在运行的应用程序 是个进程 这个应用程序又要执 ...

  8. 反射学习笔记之动态创建对象和调用方法

    动态加载和静态引用的程序集并不是同一个Assembly了.事实上,在.Net中,同一个应用程序域并不允许同时加载两个相同的Assembly.即使加载了,也会认为是两个不同的程序集.如果要同时加载两个, ...

  9. C/C++|Qt工作笔记-4种方法判断当前对象(类)名或标识(继承发,typeid法,元对象className()法,Q_CLASSINFO法)

    回想起3个月前,刚刚参加工作也做过类似的笔记,但只有2种方法,估计刚毕业没有什么墨水,经过3个月时间又多了2种方法: 这些方法都可用于RTTI 第一个方法是继承发(C++中很推荐用这个,感觉用这个结构 ...

  10. u盘启动计算机的几种方式,进入U盘启动模式的启动模式是什么?第一种方法是输入BIO...

    说到该模式,每个人都应该知道,有些朋友在引导时问如何启动U盘安装系统,有些人想问如何进入U盘来启动计算机. 到底是怎么回事?其实如何设置启动U盘启动?下面的编辑器组织了按一下启动键即可进入U盘启动模式 ...

最新文章

  1. APUE读书笔记-14高级输入输出-06异步IO
  2. python什么是调用_python open需要调用什么
  3. 扩展--使用队列来优化递归操作完成文件下载
  4. [问题解决]基于注解配置dubbo遇到ConnectionLoss for /dubbo/xxx问题解决
  5. 定位低效SQL与不同的Extra类型(转载)
  6. Bugku杂项-convert
  7. JavaScript 继承
  8. How to install VNC on Ubuntu
  9. Atitit. 解决80端口 System 占用pid 4,,找到拉个程序或者服务占用http 80服务
  10. 飞思卡尔单片机高效c语言编程,飞思卡尔单片机高效c语言编程(中文)新.pdf
  11. jmeter连接mysql并定义变量提供给后续接口使用
  12. HBase 从下载到安装和运行
  13. 一套优秀的直播系统源码是什么样的?起码要有这五个模块
  14. ABBYY软件PDF文本审阅操作之批阅文本
  15. 为什么篮球一进游戏就服务器中断,街头篮球手游进不去 进不去游戏无非这两种原因...
  16. 微信提现显示服务器异常,微信零钱提现为什么显示提示交易异常 解决办法是什么...
  17. `算法竞赛题解` LCP 03. 机器人大冒险
  18. 微信公众号留言功能实现方法分享
  19. 老王的电影网站 - 推荐系统入门(一)
  20. 怎么用手机压缩图片?教给大家三种手机压缩图片方法

热门文章

  1. andriod studio怎么设置图片大小_Word图片大小总是对不齐,如何统一图片的大小位置,看一眼就会!...
  2. typescript索引类型_TypeScript的索引类型与映射类型,以及常用工具泛型的实现
  3. modbus报文解析实例_云原生、全栈可编程的下一代SDN解析与实践 (一)丨传统SDN架构演进...
  4. git 修改分支名字_大牛总结的 Git 使用技巧,写得太好了!
  5. windows下多tomcat部署
  6. IPv6应用普及,任重而道远
  7. IDEA @Override is not allowed when implementing interface method(转载)
  8. 【腾讯Bugly干货分享】那些年,我们一起写过的“单例模式”
  9. 魅力 .NET:从 Mono、.NET Core 说起
  10. BroadcastReceive之ip拨号