原文:http://www./node/350 在我继续一系列的Qt数据序列化文章之前,有一个相对重要的需要提及的话题,那就是:基于类名动态创建类对象的能力。 假定现在我们要创建一系列的形状,形状是一个抽象类,实际类是存储在一个列表中的各种各样的派生类:矩形、圆等等。在序列化期间,我们可以保存每一项的类 名和对象数据,在反序列化(即加载数据)时,我们需要能够创建合适类实例的能力,这就是要用到一个对象工厂的地方。在支持反射的语言中,例如C#、 Java,仅需要几行代码就可以从一个跟定的类名字符串获得一个类实例。但是在c++中没有这样的机制。 一个简单的解决方案是创建一个单独的函数,里面有一个大的switch块(或者一系列的switch块)来创建合适的类对象,尽管这种方法不雅观并且破 坏了面向对象设计,但是大多数情况下是可以接受的。然而,当你有很多的类而且分散在应用程序的不同模块时,使用上述方法可能会变得难以管理,而且当应用程 序有扩展的模块和动态加载的插件时,这就会变得更加困难。 另一个更优雅的解决方法是有一个不需要知道任何对象类型的工厂,而要通过工厂实例化的类必须使用某种内部图来先注册,这样,每个模块或插件可以独立的注册它们各自类。 QT有两种可以用来创建这样的工厂的机制,它们看起来相似,实际上有很大的差别: QMetaType construct()方法能够用来创建任何内建类型的实例,或者是通过Q_DECLARE_METATYPE宏指定的自定义类型。这是 QVariant所要做的,用来内部封装自定义类型。然而这种机制是用来供变量类型使用的,也就是有默认构造和拷贝函数的类,但是对于抽象类对象是没有意 义的,因为抽象类通常使用指针传递,并且拷贝构造通常被禁用。 QMetaObject newInstance()方法可以用来创建任何一个从QObject派生下来的类的实例,仅有的条件是类的构造器必须通过Q_INVOKABLE修 饰,来明确地声明。这和多态对象配合可以很好的工作,因为QObject类通常作为各种抽象类的基类。值得注意的是,从QT4开始,若没有额外的工作,仅 仅依靠类名是不可能检索到QMetaObject的。
可以很容易的创建一个依赖于QMetaObject的对象工厂,这里有一种实现,不过这种解决方法也有一些缺点: 没有在编译期检查是否存在合适的构造函数可以访问,或者参数类型是否正确,当你实际尝试创建实例时,仅仅会得到一个运行时警告,并返回空指针; 子类化QObject会增加每个对象实例的内存占用,当执行运行时类型检查时,通过QMetaObject进行的动态方法调用也会存在一些开销。
然而,创建一个可以创建任何类的自定义类工厂也不是难事,下面是一个适用于任何继承于QObject的类的创建工厂: 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 ); } 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; } }; 使用这种途径,不在需要使用Q_INVOKABLE声明构造器了,而且如果没有找到合适的构造器,只要这个类注册了,在constructorHelper()方法中就会报告一个编译错误。而且代码很容易使用: ObjectFactory::registerClass<Foo>(); // ... QObject* foo = ObjectFactory::createObject( "Foo" ); 同时也很容易修改这个代码,来适用于那些不从QObject继承的自定义抽象类,例如它可以使用任何传递给registerClass()方法或者自动从 类的静态成员接收的类型的“Key”,而不是使用从OMetaObject接收的类名作为“Key”.根据需要还有一组不同的参数可以传递给构造函数。 |
|