Qt元对象(Meta-Object)系统与反射

发布于 2019-01-16  164 次阅读


反射

-在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。[1]用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

要注意术语“反射”和“内省”(type introspection)的关系。内省(或称“自省”)机制仅指程序在运行时对自身信息(称为元数据)的检测;反射机制不仅包括要能在运行时对程序自身信息进行检测,还要求程序能进一步根据这些信息改变程序状态或结构。

C++的反射

C++的标准语法是不提供反射的特性的,不过随着C++17的定稿,估计这个关键词不会加到标准中了。不过这个我们可以用template来实现,这次就不写template了。今天主要是讲的是Qt。

Qt的反射

Qt最大的特点就是增加了moc的过程,个人理解,Qt扩展了C++的语法,以及增强了自己的基本库。

Meta Object System is a part of Qt framework core provided to support Qt extensions to C++ like signals/slots for inter-object communication, run-time type information, and the dynamic property system.[1]
Architecture
The Meta object system consists of 3 things: QObject class, Q_OBJECT macro and a tool called moc (Meta-Object Compiler). QObject is the base class for all Qt classes, Q_OBJECT macro is used to enable meta-object features in classes and finally moc is a preprocessor that changes Q_OBJECT macro instances to C++ source code to enable meta object system mechanism in the class in which it is used.[2]
Using the meta object system has brought some criticism. In Qt documentation, several reasons have been given for the use of the meta object system, including benefits of code generation, dynamism of GUIs, automatic binding to scripting languages, not adding limitations and also reasonable performance in signal/slot implementation with moc.[3] There are some efforts to make Qt needless of a preprocessor. These efforts include re-implementing Qt moc using libclang.[4]

moc可以理解将Qt中的一些关键词,比如Q_Object ,Q_PROPERTY等转化为c++的基本语法,所以我们在编译Qt的工程时,首先要qmake->make。

个人认为Qt有两个我比较看重的特点。

  • STL基础库的扩展。
  • C++基本语法与特性的扩展。

尤其是基本语法的扩展,比如信号槽,元对象系统,让C++一个静态语言有了动态语言的特性(当然你也可以用template来实现,但是这玩意儿一般人又用不起,而且写起来也比较恶心)。这一点我就可以吹爆了。

下边开始讲Qt的反射。Qt的反射是基于Qt的元对象系统的。当然Qt还不能做到像java那样通过类名来创建一个对象,这个需要我们自己写一个工厂模式。个人认为,除了这一点,与java中的反射没有啥区别了。都可以动态的去访问其成员变量,成员函数,以及设置属性。当然必须加上Qt自己的语法。

举个栗子

//1 .继承 QObject 
class TestObject : public QObject
{
    Q_OBJECT // 2.声明Q_OBJECT
    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChange) //3. Q_PROPERTY 注册成员变量
    Q_PROPERTY(QString text MEMBER m_text NOTIFY textChange) //4. 注册的成员变量能够响应自定义的signals textChange
public:
    TestObject(QObject* parent);
    ~TestObject();
    void init();
    //------
    Q_INVOKABLE QString text(); //5.注册类的成员函数
    Q_INVOKABLE void setText(const QString& strText); //5.注册类的成员函数
    QString m_text; //类的成员变量
signals:
    void textChange(); //自定义的signals
public slots:
    void textslot(){qDebug()<<"textslot"<<endl;} //自定义的signals响应的槽函数
};

//cpp
TestObject::TestObject(QObject* parent) : QObject (parent)
{
    this->setObjectName("TestObject");
    connect(this, SIGNAL(textChange()),this, SLOT(textslot()));
}
TestObject::~TestObject()
{
}
void TestObject::init()
{
    qDebug()<<"*********************"<<endl;
    qDebug()<<"init"<<endl;
}
QString TestObject::text()
{
    return m_text;
}
void TestObject::setText(const QString& strText)
{
    if (m_text == strText)
        return;
    m_text = strText;
//    emit textChange(); //有了第四条这个语句已经不需要了。
}

这里看调用方法

    TestObject* obj = new TestObject(this);  // new一个对象

    qDebug()<<obj->objectName()<<endl; //输出对象的名字
    // custom object property
    obj->setProperty("text", "hahaha");    //设置对象的属性
    qDebug()<<obj->property("text").toString()<<endl; //输出对象的属性

    //得到注册的类成员函数
    qDebug()<<"begin--------------------custom class method"<<endl;
    const QMetaObject* mobj = obj->metaObject();

    qDebug()<<mobj->methodCount()<<endl;
    for(int i = 0; i < mobj->methodCount(); i++)
    {
        QMetaMethod mMethod = mobj->method(i);
        QByteArray byteArray = mMethod.name();
        //输出函数类型与函数名称
        qDebug()<<mMethod.typeName()<<"->"<<QString(byteArray)<<endl; 
    }
    qDebug()<<"end----------------------custom class method"<<endl;

    //调用注册的成员函数,通过Q_RETURN_ARG来获取返回值
    qDebug()<<"begin    QMetaObject::invokeMethod"<<endl;
    QString invokeString;
    //调用类的成员函数
    QMetaObject::invokeMethod(obj, "text", Qt::DirectConnection, Q_RETURN_ARG(QString, invokeString));
    qDebug()<<invokeString<<endl;
    qDebug()<<"end            QMetaObject::invokeMethod"<<endl;

    // 再次设置text值,可以响应这个信号,可以参考4
    obj->setProperty("text", "luelueluelue");

看上边的代码,C++能够随时获取当前类的成员变量与成员函数以及调用。(并不是通过C++的type id)这就是反射。反射在写GUI的时候是非常有用的。也就是我们可以随时获取当前对象的任何我们想要的属性以及想要调用的函数。静态语言有了动态语言的特性。这就是Qt强大的地方。

Qt通过类名来实现真正的反射

#include <QByteArray>
#include <QMetaObject>
#include <QHash>

#ifndef OBJECTFACTORY_H
#define OBJECTFACTORY_H

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;
    }
};

#endif // OBJECTFACTORY_H

下边是使用方法。todo ,这个东西应该写成单例模式的。

//使用方法
    ObjectFactory fac;
    fac.registerClass<TestObject>();
    qDebug()<<"begin-------------------------------"<<endl;
    TestObject* object = qobject_cast<TestObject*>(fac.createObject( "TestObject" , this));
    object->setText("template factory");
    qDebug()<<object->text()<<endl;

 

 


公交车司机终于在众人的指责中将座位让给了老太太