QT动态创建对象(第⼀种⽅法)
在我继续⼀系列的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");
();
}
使⽤这种途径,不在需要使⽤Q_INVOKABLE声明构造器了,⽽且如果没有到合适的构造器,只要这个类注册了,在constructorHelper()⽅法中就会报告⼀个编译错误。⽽且代码很容易使⽤:
1. ObjectFactory::registerClass<Foo>();
2.
3. // ...
4.
5. QObject* foo = ObjectFactory::createObject( "Foo" );
同时也很容易修改这个代码,来适⽤于那些不从QObject继承的⾃定义抽象类,例如它可以使⽤任何传递给registerClass()⽅法或者⾃动从类的静态成员接收的类型的“Key”,⽽不是使⽤从OMetaObject接收的类名作为“Key”.根据需要还有⼀组不同的参数可以传递给构造函数。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。