一、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年,不過代碼量不多。有時間,到時候,可以研究一下。
===================================================
===================================================
業餘時間不定期更新一些想法、思考文章,歡迎關注,共同探討,沉澱技術!