一、Qt怎么实现反射实例化的?
这里有2种办法,参考:《Qt通过类名称创建类的实例的两种方法》
第一种,注册元数据类型,通过类型id实例化
class Animal
{
public:
virtual void voice();
}
class Dog : public Animal
{
public:
virtual void voice();
};
// 注册类型
qRegisterMetaType<Dog>("Dog");
// 实例化
int id = QMetaType::type("Dog");
Animal *animal = static_cast<Animal*>(QMetaType::create(id));
第二种,通过QMetaObject::newInstance函数实例化
class Animal : QObject
{
public:
virtual void voice();
}
class Dog : public Animal
{
Q_OBJECT
public:
Q_INVOKABLE Dog();
virtual void voice();
};
// 注册类型
QMap<QString, QMetaObject> metaMap;
metaMap.insert("Dog", Dog::staticMetaObject);
// 实例化
Animal *animal = static_cast<Animal*>(metaMap.value("Dog").newInstance());
此种方式,限制条件:
- 必须继承自QObject;
- 必须添加Q_OBJECT宏;
- 必须对构造函数添加Q_INVOKABLE宏。
疑问:反射实例化时,如何传参?
定义Car类
class Car : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE Car(int a, double b)
{
qDebug() << "Car():" << a << b;
}
};
测试反射传参,newInstance最多支持10个参数。
// 注册类型
QMap<QString, QMetaObject> metaMap;
metaMap.insert("Car", Car::staticMetaObject);
// 实例化
metaMap.value("Car").newInstance(Q_ARG(int, 5), Q_ARG(double, 3.6));
运行结果:
二、基于模板new T()实现反射类
上面的2种反射方式,都需要先注册,后才能使用,比java里面稍微麻烦一点。
目前找到一个现成的轮子,他不属于上面的2种方式,而是使用模板方式(new T())来实现的,遂决定在此基础上改改。
轮子参考:《Qt根据类名创建对象(元对象反射)》
我们在此基础上,增加对构造函数不定参数的支持。修改后的代码,如下:
template <typename... Args>
class Reflect
{
public:
template<typename T>
static void registerClass()
{
constructors().insert( T::staticMetaObject.className(), &constructorHelper<T> );
}
static QObject* newInstance( const QByteArray& className, Args... args )
{
Constructor constructor = constructors().value( className );
if ( constructor == nullptr )
return nullptr;
return (*constructor)( std::forward<Args>(args)... );
}
private:
typedef QObject* (*Constructor)( Args... );
template<typename T>
static QObject* constructorHelper(Args... args )
{
return new T( std::forward<Args>(args)... );
}
static QHash<QByteArray, Constructor>& constructors()
{
static QHash<QByteArray, Constructor> instance;
return instance;
}
};
限制条件:
- 必须继承自QObject;
- 必须添加Q_OBJECT宏。
测试代码,如下:
Print.h中,构造函数有3个参数。
class Print : public QObject
{
Q_OBJECT
public:
Print(QObject* parent, int a, double b)
:QObject(parent)
{
qDebug() << "constructor..." << a << b;
}
void func()
{
qDebug() << "func...";
}
};
main.cpp
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 注册类型
Reflect<QObject*, int, double>::registerClass<Print>();
// 实例化
QObject* obj = Reflect<QObject*, int, double>::newInstance("Print", nullptr, 5, 3.6);
Print* object = static_cast<Print*>(obj);
object->func();
return a.exec();
}
运行结果:
代码地址:
https://gitee.com/bailiyang/cdemo/tree/master/Qt/35Reflect/ReflectParam
目标达成,实现构造函数实例化传参。
不过嘛,就是使用的时候,样子稍微麻烦了些。
下面使用QMetaObject::newInstance函数来实现反射类。
三、基于QMetaObject::newInstance实现反射类
代码比较简单,也支持构造函数传参。
class Reflect
{
public:
template<typename T>
static void registerClass()
{
metaObjects().insert( T::staticMetaObject.className(), T::staticMetaObject );
}
static QObject* newInstance( const QByteArray& className,
QGenericArgument val0 = QGenericArgument(nullptr),
QGenericArgument val1 = QGenericArgument(),
QGenericArgument val2 = QGenericArgument(),
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument(),
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument(),
QGenericArgument val9 = QGenericArgument() )
{
Q_ASSERT( metaObjects().contains(className) );
return metaObjects().value(className).newInstance(val0, val1, val2, val3, val4,
val5, val6, val7, val8, val9);
}
private:
static QHash<QByteArray, QMetaObject>& metaObjects()
{
static QHash<QByteArray, QMetaObject> instance;
return instance;
}
};
限制条件:
- 必须继承自QObject;
- 必须添加Q_OBJECT宏;
- 必须对构造函数添加Q_INVOKABLE宏。
测试代码,如下:
class Car : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE Car()
{
qDebug() << "Car()";
}
Q_INVOKABLE Car(int a, double b)
{
qDebug() << "Car():" << a << b;
}
void run()
{
qDebug() << "I'm running";
}
};
main.cpp
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 注册类型
Reflect::registerClass<Car>();
// 实例化Car,无参构造函数
Car* car1 = static_cast<Car*>(Reflect::newInstance("Car"));
car1->run();
// 实例化Car,带参构造函数
Car* car2 = static_cast<Car*>( Reflect::newInstance("Car", Q_ARG(int, 5), Q_ARG(double, 2.6)) );
car2->run();
return a.exec();
}
运行结果:
代码地址:
https://gitee.com/bailiyang/cdemo/tree/master/Qt/35Reflect/QtReflect
四、总结与思考
只要实现了反射功能,可玩性,就会得到极大提高。像java里面很多框架如spring等,都是基于反射实现的,主要思想控制反转(IOC)与依赖注入(DI),简直是复杂应用开发的大杀器。
核心思想就是通过字符串,实例化对象。而字符串在配置文件xml中,可以通过配置xml文件,指定构造器参数,属性等,也可以注入属性依赖对象,这些一切的一切都是写xml实现的,灵活性很强。
在近期的项目开发中,也遇到了类似的需求,需要创建大量的对象,用于执行。而这些对象的描述,就是属性需要保存到配置文件中,自然想到,将属性保存到配置文件中,后通过反射自动实例化,并初始化各项成员。这样的好处是,可以实现所有对象的统一创建和初始化,且易于扩展。
后续,有时间,再把这部分内容,以博客形式分享一下。
找了下Qt下的IOC框架,找到一个,叫QtIOCContainer,但是时间久远,最后一次更新好像是2006年,不过代码量不多。有时间,到时候,可以研究一下。
===================================================
===================================================
业余时间不定期更新一些想法、思考文章,欢迎关注,共同探讨,沉淀技术!