关于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年,不过代码量不多。有时间,到时候,可以研究一下。


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

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

业余时间不定期更新一些想法、思考文章,欢迎关注,共同探讨,沉淀技术!

            

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