什么是反射?

反射是指在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。在 Java 中,反射是通过JVM实现的。JVM在得到class对象之后,再通过对class对象进行反编译,从而获取对象的各种信息。

可以参考 廖雪峰的官方网站 来帮助理解。Java 不是我们这篇文章的重点。

简单来说,如果编译不知道类或对象的具体信息,比如我们在搭 Java的SSH或者SSM框架的时候,类的名称放在XML文件中,属性和属性值放在XML文件中,需要在运行时读取XML文件,动态获取类的信息,此时就可以用反射来实现。

下面的代码来自 Java反射机制-十分钟搞懂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static void main(String[] args) throws Exception {
//编码/编译的时候,已经知道要创建哪个类的对象,此时和反射没关系
Animal an = new Cat();
an.nickName ="旺财";
an.color = "黑色";
an.shout();
System.out.println(an);
//编码/编译的时候,不知道要创建哪个类的对象,只有根据运行时动态获取的内容来创建对象
//使用Properties类读取属性文件,最终得到了类的完整路径字符串
String className = "com.bjsxt.why.Cat";
//创建对象
Class clazz = Class.forName(className);
Object an2 = clazz.newInstance();
//操作属性
//执行方法
}
}

但是在C++ 中,C++ 只有非常弱的反射RTTI,但是C++ 的一些框架比如QT,UE自己实现了反射。这里参考 写给 C++ 程序员的反射教程

Qt 中使用反射

reflect.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#pragma once

#include <QMetaObject>
#include <QHash>

class Reflect
{
public:
template<typename T>
static void registerClass()
{
if(!constructors().contains(T::staticMetaObject.className()))
{
constructors().insert( T::staticMetaObject.className(), &constructorHelper<T> );
}
}

static QObject* newInstance( const QByteArray& className, QObject* parent = nullptr )
{
Constructor constructor = constructors().value( className );
if ( constructor == nullptr )
return nullptr;
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;
}
};

想要实现反射的类要继承 QObject 并添加 Q_OBJECT,为了能使类检测到成员变量和函数,还需要在变量和函数前面添加宏 Q_INVOKABLE,这意味着该变量或函数在元对象系统编译该类时进行注册,在运行过程中能被元对象调用。例如:

dog.h

1
2
3
4
5
6
7
8
9
10
11
12
#pragma once

#include <QObject>

class Dog : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE explicit Dog(QObject* parent = nullptr);

Q_INVOKABLE void func();
};

dog.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "dog.h"
#include <QDebug>

Dog::Dog(QObject* parent)
: QObject(parent)
{
qDebug() << "instance created";
}

void Dog::func()
{
qDebug() << "fun loaded";
}

使用的时候如下:

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "dog.h"
#include "reflect.h"

#include <QApplication>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);

Reflect::registerClass<Dog>();



QObject* obj = Reflect::newInstance("Dog");
QMetaObject::invokeMethod(obj, "func");

return a.exec();
}

可以看出,Qt的这种反射并不是真正意义上的反射,使用之前需要先进行注册。虽然可以在一个启动函数中注册所有的类,但还是比较麻烦的。

但是,通过将反射与Qt的property相结合,我们能够将类实现成类似于 Java 中的 bean 的东西,通过 get 和 set 方法取值和赋值。