關於Qt實現類反射的一些思考與總結

一、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年,不過代碼量不多。有時間,到時候,可以研究一下。


===================================================

===================================================

業餘時間不定期更新一些想法、思考文章,歡迎關注,共同探討,沉澱技術!

            

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章